索引 - 优化查询¶
我们刚刚学习了如何获取满足 WHERE **条件** 的数据。例如,英雄**名字是“Deadpond”**的数据。
如果我们像之前一样创建表和数据,当我们使用 WHERE 语句 SELECT 数据时,数据库必须**扫描** **每一条记录**才能找到匹配的记录。对于这几个例子中的 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 的字母 d 位于代表 manual 的字母 m **之前**。
所以,您知道您必须在当前位置**之前**的词典中搜索。您仍然不知道单词 database 在哪里,因为您不知道字母 d 在词典中的确切位置,但您知道**它不会在**那个位置之后,您现在可以在搜索中**丢弃词典的右半部分**。
接下来,您**再次打开词典**,但只考虑可能包含您想要单词的**词典的一半**,即**词典的左半部分**。您打开它的中间,现在您可能到达了字母 f。
您知道 database 中的 d 在 f 之前。所以它必须在**之前**。但现在您知道 database **不在**那个点之后,您可以从那个点开始丢弃词典。
现在您有一**小部分词典**要搜索(只有**四分之一**的词典可能包含您的单词)。您拿起词典开头那**四分之一**可能包含您单词的页面,并打开该部分的中间。也许您会到达字母 c。
您知道单词 database 必须在那个点**之后**,而**不在之前**,所以您可以丢弃该页块的左半部分。
您重复这个过程**几次**,最终到达字母 d,您继续在该部分使用相同的过程查找字母 d,最终**找到单词** database。🎉
您不得不翻开词典几次,也许是**5 到 10 次**。这实际上是**非常少的工作**,与可能的情况相比。
技术细节
您喜欢**华丽的词语**吗?酷!程序员往往喜欢华丽的词语。😅
我上面给您展示的那个算法叫做**二分查找**。
之所以这样命名,是因为您通过将词典(或任何有序的事物列表)分成**两**部分(“二分”的意思是“二”)来**搜索**某些东西。然后您多次重复这个过程,直到找到您想要的东西。
索引和小说¶
现在让我们想象您正在读一本**小说**。有人告诉您,在某个地方,他们提到了**数据库**,您想找到那一章。
您如何找到“database”这个词呢?您可能需要**阅读整本书**才能找到“database”这个词在书中的位置。因此,您不是打开书 5 或 10 次,而是必须打开 **500 页**中的每一页,一页一页地阅读,直到找到这个词。不过,您可能会喜欢这本书。😅
但如果我们只关心**快速查找信息**(就像使用 SQL 数据库时一样),那么阅读每 500 页是**效率太低**的,而如果可以选择打开书 5 或 10 个地方就能找到您要找的东西,那会更好。
带索引的技术书籍¶
现在让我们想象您正在阅读一本技术书籍。例如,其中有几个关于编程的主题。并且有几节谈到了**数据库**。
这本书可能有一个**图书索引**:书中的一个部分,其中包含一些**主题名称**以及可以在书中阅读这些主题的**页码**。主题名称按字母顺序**排序**,很像词典(一本包含单词的书,如上一个示例)。
在这种情况下,您可以在书的末尾(或开头)打开书,找到**图书索引**部分,它只有几页。然后,您可以像上面**词典**示例一样进行相同的过程。
打开索引,经过**5 到 10 步**,快速找到主题“**数据库**”,并找到涵盖该主题的页码,例如“第 5 章第 253 页”。现在您使用词典技术查找了**主题**,并且该主题为您提供了**页码**。
现在您知道您需要找到“**第 253 页**”。但是通过查看合上的书,您仍然不知道那页在哪里,所以您必须**找到那页**。要找到它,您可以再次执行相同的过程,但这次,您不是在**索引**中搜索**主题**,而是在**整本书**中搜索**页码**。再经过**5 到 10 个步骤**,您找到了第 5 章的第 253 页。
在此之后,尽管这本书不是词典,并且有一些特定的内容,但您能够在**几个步骤**内(例如 10 或 20 步,而不是阅读所有 500 页)**找到**书中谈论“**数据库**”的**部分**。
主要一点是索引是**排序**的,所以我们可以使用与**词典**相同的过程来查找主题。然后这会给我们一个页码,而且**页码也是排序的**!😅
当我们有一系列已排序的事物时,我们可以应用相同的技术,这就是这里的全部诀窍,我们首先对索引中的**主题**使用相同的技术,然后对**页码**使用相同的技术来查找实际的章节。
效率真高!😎
什么是数据库索引¶
**数据库索引**与**书籍索引**非常相似。
数据库索引以一种**易于快速查找**(例如排序)的方式存储一些信息,一些键,然后对于每个键,它们**指向数据库中其他地方的一些数据**。
让我们看一个更清晰的例子。假设您的数据库中有这个表
| id | name | secret_name | age |
|---|---|---|---|
| 1 | 死侍 | 戴夫·威尔逊 | 空 |
| 2 | 蜘蛛男孩 | 佩德罗·帕尔克多 | 空 |
| 3 | 锈人 | 汤米·夏普 | 48 |
我们想象您有**更多行**,更多英雄。可能是**数千个**。
如果您告诉 SQL 数据库按特定名称(例如 Spider-Boy,通过在 SQL 查询的 WHERE 部分使用 name)获取英雄,数据库将必须**扫描**所有英雄,**逐一**检查以找到所有名称为 Spider-Boy 的英雄。
在这种情况下,只有一个,但没有什么限制数据库拥有**更多同名记录**。因此,数据库将**继续搜索**并检查每一条记录,这将非常慢。
但现在假设数据库为 name 列创建了一个索引。这个索引可能看起来像这样,我们可以想象索引就像数据库自动管理的一个额外的特殊表
| name | id |
|---|---|
| 死侍 | 1 |
| 锈人 | 3 |
| 蜘蛛男孩 | 2 |
它将按**顺序**排列 hero 表中的每个 name 字段。它不会按 id 排序,而是按 name 排序(按字母顺序,因为 name 是一个字符串)。所以,首先是 Deadpond,然后是 Rusty-Man,最后是 Spider-Boy。它还会包含每个英雄的 id。请记住,这可能包含**数千**个英雄。
然后数据库将能够或多或少地使用上面**词典**和**书本索引**示例中的相同思想。
它可以从某个地方开始(例如,索引的中间)。它可能会到达中间的某个英雄,例如 Rusty-Man。而且由于**索引**按顺序排列了 name 字段,数据库将知道它可以**丢弃所有先前的索引行**,并且**只搜索**接下来的索引行。
| name | id |
|---|---|
| 死侍 | 1 |
| 锈人 | 3 |
| 蜘蛛男孩 | 2 |
这样,正如上面词典的例子一样,**数据库无需读取成千上万个英雄**,而是只需几个步骤,例如**5 到 10 步**,就能到达包含 Spider-Boy 的索引行,即使表(和索引)有成千上万行
| name | id |
|---|---|
| 死侍 | 1 |
| 锈人 | 3 |
| ✨ 蜘蛛男孩 ✨ | 2 |
然后通过查看**此索引行**,它将知道 hero 表中 Spider-Boy 的 id 是 2。
然后它就可以或多或少地使用**相同技术**在 hero 表中**搜索该 id**。
这样,最终,数据库只需**几个步骤**就能找到我们想要的英雄,而不是读取成千上万条记录。
更新索引¶
正如您所想,为了让这一切正常工作,索引需要与数据库中的数据**保持最新**。
如果您必须在代码中**手动**更新它,那将非常繁琐且**容易出错**,因为很容易导致索引未及时更新并指向不正确的数据。😱
好消息是:当您在 **SQL 数据库**中创建索引时,数据库会负责在必要时**自动更新**它。😎🎉
如果您向 hero 表中**添加新记录**,数据库将**自动**更新索引。它将执行**查找**新索引数据正确位置的**相同过程**(上述**5 到 10 个步骤**),然后它将在那里保存新的索引信息。当您**更新**或**删除**数据时,也会发生同样的情况。
使用 SQL 数据库定义和创建索引非常**容易**。而**使用它**甚至更容易...它是透明的。数据库将自动确定使用哪个索引,SQL 查询甚至不需要更改。
所以,在 SQL 数据库中,**索引非常棒**!而且**使用起来**超级**简单**。为什么不为所有东西都建立索引呢?.....因为索引在计算和存储(磁盘空间)方面也有“**成本**”。
索引成本¶
**索引**与**成本**相关。💰
当您没有索引并向 hero 表中添加**新行**时,数据库需要执行**1 次操作**才能将新英雄行添加到表的末尾。
但是,如果您为**英雄名称**建立了一个**索引**,现在数据库必须执行相同的**1 次操作**来添加该行,**加上**索引中的额外**5 到 10 次操作**,以找到名称的正确位置,然后将该**索引记录**添加到那里。
如果您有一个用于 name 的索引,一个用于 age 的索引,以及一个用于 secret_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。此索引应位于
hero表**上**,它指向该表。我希望你使用的列是
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 部分使用任何其他字段),您可能至少需要为该字段建立一个**索引**。
回顾¶
**索引**对于提高查询数据库时的**读取性能**和速度非常重要。🏎
创建和使用它们非常**简单**和容易。最重要的部分是理解它们**如何**工作,**何时**创建它们以及为**哪些列**创建它们。