跳到内容

索引 - 优化查询

我们刚刚看到了如何获取一些数据,其中 WHERE 条件为真。例如,英雄的名字是 "Deadpond" 的情况。

如果我们像之前那样创建表和数据,当我们使用 WHERESELECT 一些数据时,数据库将不得不扫描每个记录,以找到那些匹配的记录。在这些示例中,只有 3 个英雄,这不是问题。

但想象一下,你的数据库有成千上万数百万记录,如果每次你想找到名字为 "Deadpond" 的英雄时,它都必须扫描所有记录以找到所有可能的匹配项,那就会变得有问题,因为它会太慢。

我将向你展示如何使用数据库索引来处理它。

代码中的更改非常小,但了解幕后发生的事情很有用,所以我将向你展示它是如何工作的以及它的含义。


如果你已经执行了之前的示例并拥有一个带有数据的数据库,请在运行每个示例之前删除数据库文件,这样你就不会有重复的数据,并且能够获得相同的结果。

没有时间解释

你已经是 SQL 专家,没有时间看我所有的解释了吗?

好吧,在这种情况下,你可以偷看一下创建索引的最终代码。

👀 完整文件预览
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
    hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
    hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
    hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
    hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)
        session.add(hero_4)
        session.add(hero_5)
        session.add(hero_6)
        session.add(hero_7)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.age <= 35)
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
    hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
    hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
    hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
    hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)
        session.add(hero_4)
        session.add(hero_5)
        session.add(hero_6)
        session.add(hero_7)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.age <= 35)
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()

...但如果你不是专家,继续阅读,这可能会很有用。🤓

什么是索引

一般来说,索引只是我们可以用来帮助我们更快找到东西的东西。它通常通过将事物排序来工作。在考虑数据库和代码之前,让我们先考虑一些现实生活中的例子。

索引和字典

想象一下字典,一本包含单词定义的书。 📔 ...不是 Python 的 dict。 😅

假设你想找到一个单词,例如单词 “database”。你拿起字典,随便翻开一页,例如在中间。也许你看到了一些以 m 开头的单词的定义,例如 manual,所以你得出结论,你是在字典中字母 m 的部分。

你知道在字母表中,database 的字母 dmanual 的字母 m 之前

所以,你知道你必须在字典中在你当前所在位置之前搜索。你仍然不知道单词 database 在哪里,因为你不知道字母 d 在字典中的确切位置,但你知道它不在那个位置之后,你现在可以在搜索中丢弃字典的右半部分

接下来,你再次打开字典,但只考虑可能包含你要找的单词的字典的一半,即字典的左半部分。你在左半部分中间打开它,现在你可能到达了字母 f

你知道 databasedf 之前。所以它必须在之前。但现在你知道 database 不在那个位置之后,你可以从那个位置开始丢弃字典。

现在你有一个小部分的字典要搜索(只有四分之一的字典可能包含你的单词)。你拿出字典开头可能包含你的单词的四分之一的页面,并在该部分的中间打开它。也许你到达了字母 c

你知道单词 database 必须在之后而不是那个位置之前,所以你可以丢弃该页块的左半部分。

你重复这个过程几次,你最终到达了字母 d,你继续在字母 d 的部分中进行相同的过程,你最终找到了单词 database。 🎉

你必须打开字典几次,也许 5 或 10 次。与可能的情况相比,这实际上是非常少的工作

技术细节

你喜欢花哨的词语吗?酷!程序员往往喜欢花哨的词语。 😅

我上面向你展示的 算法 称为 二分查找

之所以这样称呼它,是因为你通过将字典(或任何有序的事物列表)分成部分(“二分”意味着“二”)来搜索某物。你多次执行该过程,直到找到你想要的东西。

索引和小说

现在让我们想象一下你在读一本小说。有人告诉你,在某个时候,他们提到了一个 database,你想找到那一章。

你如何在其中找到单词 “database”?你可能必须阅读整本书才能找到单词 “database” 在书中的位置。因此,你可能需要打开 500 页中的每一页并逐页阅读,直到找到该单词,而不是打开书 5 或 10 次。不过,你可能会喜欢这本书。 😅

但是,如果我们只对快速查找信息感兴趣(就像使用 SQL 数据库时一样),那么当可以选择打开书 5 或 10 个地方并找到你要查找的内容时,阅读 500 页中的每一页效率太低

带有索引的技术书籍

现在让我们想象一下你在读一本技术书籍。例如,其中有几个关于编程主题的章节。并且有几节讨论了 database

这本书可能有一个书籍索引:本书中的一个部分,其中包含一些涵盖的主题名称以及你可以在书中阅读它们的页码。主题名称按字母顺序排序,很像字典(一本包含单词的书,如前面的示例中所示)。

在这种情况下,你可以打开本书的末尾(或开头)以找到书籍索引部分,它只有几页。然后,你可以执行与上面字典示例相同的过程。

打开索引,在 5 或 10 个步骤之后,快速找到主题 “database”,其中包含涵盖该主题的页码,例如 “第 5 章第 253 页”。现在你使用了字典技术来查找主题,该主题为你提供了页码

现在你知道你需要找到 “第 253 页”。但是通过查看合上的书,你仍然不知道该页在哪里,所以你必须找到该页。要找到它,你可以再次执行相同的过程,但这次,你不是在索引中搜索主题,而是在整本书中搜索页码。在再经过 5 或 10 个步骤之后,你找到了第 5 章第 253 页。

之后,即使这本书不是字典,并且有一些特定的内容,你仍然能够在几个步骤(例如 10 或 20 步,而不是阅读所有 500 页)中找到书中讨论 “database” 的部分

关键是索引是排序的,所以我们可以使用与字典相同的过程来查找主题。然后,它给了我们一个页码,而页码也是排序的! 😅

当我们有一个排序的事物列表时,我们可以应用相同的技术,这就是这里的整个技巧,我们首先对索引中的主题使用相同的技术,然后对页码使用相同的技术来找到实际章节。

效率真高! 😎

什么是数据库索引

数据库索引书籍索引非常相似。

数据库索引以使其易于快速查找(例如排序)的方式存储一些信息,一些键,然后对于每个键,它们指向数据库中其他位置的一些数据

让我们看一个更清晰的例子。假设你在数据库中有下表

idnamesecret_nameage
1DeadpondDive Wilsonnull
2Spider-BoyPedro Parqueadornull
3Rusty-ManTommy Sharp48

让我们想象一下你有更多行,更多英雄。可能成千上万

如果你告诉 SQL 数据库通过特定名称(例如 Spider-Boy)获取英雄(通过在 SQL 查询的 WHERE 部分中使用 name),数据库将不得不扫描所有英雄,逐个检查以找到所有名称为 Spider-Boy 的英雄。

在这种情况下,只有一个,但是没有任何限制数据库拥有更多具有相同名称的记录。正因为如此,数据库将继续搜索并检查每个记录,这将非常慢。

但现在假设数据库为 name 列创建了索引。索引可能看起来像这样,我们可以想象索引就像数据库自动管理的附加特殊表

nameid
Deadpond1
Rusty-Man3
Spider-Boy2

它将包含 hero 表中的每个 name 字段按顺序排列。它不会按 id 排序,而是按 name 排序(按字母顺序,因为 name 是字符串)。因此,首先它将是 Deadpond,然后是 Rusty-Man,最后是 Spider-Boy。它还将包括每个英雄的 id。请记住,这可能有成千上万的英雄。

然后数据库将能够使用与上面示例中字典书籍索引中更或更少相同的想法。

它可以从某个地方开始(例如,在索引的中间)。它可能会到达索引中间的某个英雄,例如 Rusty-Man。并且由于索引具有按顺序排列的 name 字段,因此数据库会知道它可以丢弃所有先前的索引行,而仅在以下索引行中搜索

nameid
Deadpond1
Rusty-Man3
Spider-Boy2

这样,就像上面字典的示例一样,数据库将能够执行几个步骤,例如 5 或 10 步,而不是读取数千个英雄,并到达具有 Spider-Boy 的索引行,即使表(和索引)有数千行

nameid
Deadpond1
Rusty-Man3
✨ Spider-Boy ✨2

然后,通过查看此索引行,它将知道 hero 表中 Spider-Boyid2

因此,然后它可以使用或多或少相同的技术hero 表中搜索该 id

这样,最终,数据库只需执行几个步骤即可找到我们想要的英雄,而不是读取数千条记录。

更新索引

你可以想象,为了使所有这些工作正常进行,索引需要与数据库中的数据保持最新

如果你必须在代码中手动更新它,那将非常麻烦且容易出错,因为它很容易最终处于索引不是最新的状态并指向不正确的数据。 😱

好消息是:当你在 SQL 数据库中创建索引时,数据库会负责在必要时自动更新它。 😎🎉

如果你向 hero添加新记录,数据库将自动更新索引。它将执行相同的过程,即查找放置新索引数据的正确位置(上面描述的5 或 10 个步骤),然后在那里保存新的索引信息。当你更新删除数据时,也会发生同样的情况。

使用 SQL 数据库定义和创建索引非常容易。然后使用它甚至更容易……它是透明的。数据库将自动找出要使用的索引,SQL 查询甚至不会更改。

因此,在 SQL 数据库中,索引非常棒!并且超级易于使用。为什么不为所有内容都设置索引呢? .....因为索引在计算和存储(磁盘空间)方面也有“成本”。

索引成本

索引 相关联的成本。 💰

当没有索引并将新行添加到表 hero 时,数据库必须执行 1 次操作才能将新的英雄行添加到表的末尾。

但是,如果你为英雄名字创建了索引,那么数据库现在必须执行相同的 1 次操作来添加该行,外加索引中的额外 5 或 10 次操作,以找到名称的正确位置,然后在那里添加索引记录

如果你为 nameagesecret_name 各创建一个索引,那么数据库现在必须执行相同的 1 次操作来添加该行,外加索引中的额外 5 或 10 次操作乘以 3,即每个索引的操作次数。这意味着现在添加一行大约需要 31 次操作

这也意味着你正在交换读取数据所需的时间,以换取写入数据所需的时间,外加数据库中的一些额外 空间

如果你的查询从数据库中获取数据时,比较了这些字段中的每一个(例如使用 WHERE),那么为每个字段创建索引是完全有道理的。因为创建或更新数据时的 31 次操作(加上索引的空间)比可能需要 500 或 1000 次操作来读取所有行以便能够使用每个字段进行比较要好得多。

但是,如果你从不进行通过 secret_name 查找记录的查询(你从不在 WHERE 部分使用 secret_name),那么为 secret_name 字段/列创建索引可能没有意义,因为这将增加写入和更新数据库的计算和空间成本

使用 SQL 创建索引

哎呀,这是一大堆理论和解释。 😅

关于索引,最重要的是理解它们,如何以及何时使用它们。

现在让我们看看创建 索引SQL 语法。它非常简单

CREATE INDEX ix_hero_name
ON hero (name)

这或多或少意味着

嘿 SQL 数据库 👋,请为我 CREATE 一个 INDEX

我希望索引的名称为 ix_hero_name

此索引应 ONhero,它引用该表。

我希望你用于它的列是 name

使用 SQLModel 声明索引

现在让我们看看如何在 SQLModel 中定义索引。

代码中的更改令人失望,它非常简单。 😆

这是我们之前拥有的 Hero 模型

from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None

# Code below omitted 👇
👀 完整文件预览
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: int | None = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Deadpond")
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    secret_name: str
    age: Optional[int] = None


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Deadpond")
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()

现在让我们更新它,告诉 SQLModel 在创建表时为 name 字段创建索引

from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)

# Code below omitted 👇
👀 完整文件预览
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Deadpond")
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Deadpond")
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()

我们再次使用相同的 Field(),就像之前一样,并设置 index=True。就是这样! 🚀

请注意,我们没有设置 default=None 或任何类似的参数。这意味着 SQLModel(感谢 Pydantic)将保持它作为必需字段。

信息

SQLModel(实际上是 SQLAlchemy)将自动为你生成索引名称

在这种情况下,生成的名称将是 ix_hero_name

查询数据

现在,要使用字段 name 和新索引查询数据,我们无需在代码中执行任何特殊或不同的操作,它只是相同的代码

SQL 数据库将自动找出它。 ✨

这很棒,因为它意味着索引非常易于使用。但起初也可能会感觉违反直觉,因为你没有在代码中显式执行任何操作来使其明显索引很有用,这一切都发生在幕后的数据库中。

# Code above omitted 👆

def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Deadpond")
        results = session.exec(statement)
        for hero in results:
            print(hero)

# Code below omitted 👇
👀 完整文件预览
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Deadpond")
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.name == "Deadpond")
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()

这与我们之前的代码完全相同,但现在数据库将在后台使用索引

运行程序

如果你现在运行程序,你将看到如下输出

$ python app.py

// Some boilerplate output omitted 😉

// Create the table
CREATE TABLE hero (
        id INTEGER,
        name VARCHAR NOT NULL,
        secret_name VARCHAR NOT NULL,
        age INTEGER,
        PRIMARY KEY (id)
)

// Create the index 🤓🎉
CREATE INDEX ix_hero_name ON hero (name)

// The SELECT with WHERE looks the same
INFO Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
FROM hero
WHERE hero.name = ?
INFO Engine [no key 0.00014s] ('Deadpond',)

// The resulting hero
secret_name='Dive Wilson' age=None id=1 name='Deadpond'

更多索引

我们将查询 hero 表,对 age 字段也进行比较,因此我们也应该为该字段定义一个索引

from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)

# Code below omitted 👇
👀 完整文件预览
from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: int | None = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
    hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
    hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
    hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
    hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)
        session.add(hero_4)
        session.add(hero_5)
        session.add(hero_6)
        session.add(hero_7)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.age <= 35)
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()
🤓 其他版本和变体
from typing import Optional

from sqlmodel import Field, Session, SQLModel, create_engine, select


class Hero(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str = Field(index=True)
    secret_name: str
    age: Optional[int] = Field(default=None, index=True)


sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


def create_heroes():
    hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
    hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
    hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
    hero_4 = Hero(name="Tarantula", secret_name="Natalia Roman-on", age=32)
    hero_5 = Hero(name="Black Lion", secret_name="Trevor Challa", age=35)
    hero_6 = Hero(name="Dr. Weird", secret_name="Steve Weird", age=36)
    hero_7 = Hero(name="Captain North America", secret_name="Esteban Rogelios", age=93)

    with Session(engine) as session:
        session.add(hero_1)
        session.add(hero_2)
        session.add(hero_3)
        session.add(hero_4)
        session.add(hero_5)
        session.add(hero_6)
        session.add(hero_7)

        session.commit()


def select_heroes():
    with Session(engine) as session:
        statement = select(Hero).where(Hero.age <= 35)
        results = session.exec(statement)
        for hero in results:
            print(hero)


def main():
    create_db_and_tables()
    create_heroes()
    select_heroes()


if __name__ == "__main__":
    main()

在这种情况下,我们希望 age 的默认值继续为 None,因此我们在使用 Field() 时设置 default=None

现在,当我们使用 SQLModel 创建数据库和表时,它还将在 hero 表中为这两列创建 索引

因此,当我们查询数据库中的 hero 表并使用这两列来定义我们获取的数据时,数据库将能够使用这些索引来提高读取性能。 🚀

主键和索引

你可能注意到我们没有为 id 字段设置 index=True

因为 id 已经是主键,数据库将自动为其创建内部 索引

数据库始终自动为主键创建内部索引,因为它们是组织、存储和检索数据的主要方式。 🤓

但是,如果你想频繁查询 SQL 数据库中的任何其他字段(例如在 WHERE 部分中使用任何其他字段),你可能需要至少为该字段创建一个 索引

回顾

索引对于提高查询数据库时的读取性能和速度非常重要。 🏎

创建和使用它们非常简单和容易。最重要的部分是了解它们如何工作,何时创建它们以及为哪些列创建它们。