UUID (通用唯一标识符)¶
我们已经讨论了一些数据类型,例如 str
、int
等。
还有另一种数据类型叫做 UUID
(通用唯一标识符)。
您可能已经见过 UUID,例如在 URL 中。它们看起来像这样
4ff2dab7-bffe-414d-88a5-1826b9fea8df
UUID 特别适用于作为自增整数的替代方案,用于 主键。
信息
SQLModel 版本 0.0.20
中添加了对 UUID 的官方支持。
关于 UUID¶
UUID 是 128 位的数字,也就是 16 字节。
它们通常显示为 32 个 十六进制 字符,用破折号分隔。
UUID 有几个版本,一些版本在字节中包含当前时间,但 UUID 版本 4 主要是随机的,它们的生成方式使得它们几乎是 唯一 的。
分布式 UUID¶
您可以在一台计算机上生成一个 UUID,而另一个人可以在另一台计算机上生成另一个 UUID,并且这两个 UUID 相同 的可能性几乎是 不可能 的。
这意味着您不必等待数据库为您生成 ID,您可以在 在代码中生成它,然后再发送到数据库,因为您可以非常肯定它是唯一的。
技术细节
因为可能的 UUID 数量非常庞大 (2^128),所以两次生成相同的 UUID 版本 4(随机版本)的概率非常低。
如果您在数据库中存储了 103 万亿个版本 4 的 UUID,则生成重复的新 UUID 的概率为十亿分之一。 🤓
出于同样的原因,如果您决定迁移数据库、将其与另一个数据库合并并混合记录等等,您很可能能够 直接使用 您最初拥有的 相同 UUID。
警告
仍然有可能发生冲突,但可能性非常低。在大多数情况下,您可以假设不会发生冲突,但最好为此做好准备。
UUID 防止信息泄露¶
因为 UUID 版本 4 是 随机 的,您可以将这些 ID 提供给应用程序用户或其他系统,而不会泄露 关于您的应用程序的 信息。
当对主键使用 自增整数 时,您可能会隐式地泄露关于您的系统的信息。例如,有人可以创建一个新的 hero,并通过获取 hero ID 20
,他们会知道您的系统中有 20 个 heroes(或者更少,如果某些 heroes 已经被删除)。
UUID 存储¶
由于 UUID 是 16 字节,它们将比更小的自增整数(通常为 4 字节)消耗更多空间 在数据库中。
根据您使用的数据库,UUID 可能具有 更好或更差的性能。如果您对此感到担忧,您应该查看特定数据库的文档。
SQLite 没有特定的 UUID 类型,因此它会将 UUID 存储为字符串。其他数据库(如 Postgres)具有特定的 UUID 类型,这将比字符串带来更好的性能和空间利用率。
带有 UUID 的模型¶
要使用 UUID 作为主键,我们需要导入 uuid
,它是 Python 标准库的一部分(我们不必安装任何东西),并使用 uuid.UUID
作为 ID 字段的 类型。
我们还希望 Python 代码在创建新实例时 生成一个新的 UUID,因此我们使用 default_factory
。
参数 default_factory
接受一个函数(或者通常是一个 "可调用对象")。这个函数将在 创建模型的新实例时被调用,并且该函数返回的值将用作该字段的默认值。
对于 default_factory
中的函数,我们传递 uuid.uuid4
,这是一个生成 新的 UUID 版本 4 的函数。
提示
我们不在代码中自己调用 uuid.uuid4()
(我们不放括号)。相反,我们传递函数本身,只是 uuid.uuid4
,以便 SQLModel 可以在我们每次创建新实例时调用它。
这意味着 UUID 将在 Python 代码中生成,在将数据发送到数据库之前。
import uuid
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: int | None = Field(default=None, index=True)
# Code below omitted 👇
👀 完整文件预览
import uuid
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, 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_hero():
with Session(engine) as session:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
print("The hero before saving in the DB")
print(hero)
print("The hero ID was already set")
print(hero.id)
session.add(hero)
session.commit()
session.refresh(hero)
print("After saving in the DB")
print(hero)
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
statement = select(Hero).where(Hero.id == hero_id)
selected_hero = session.exec(statement).one()
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
def main() -> None:
create_db_and_tables()
create_hero()
select_hero()
if __name__ == "__main__":
main()
🤓 其他版本和变体
import uuid
from typing import Union
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Union[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_hero():
with Session(engine) as session:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
print("The hero before saving in the DB")
print(hero)
print("The hero ID was already set")
print(hero.id)
session.add(hero)
session.commit()
session.refresh(hero)
print("After saving in the DB")
print(hero)
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
statement = select(Hero).where(Hero.id == hero_id)
selected_hero = session.exec(statement).one()
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
def main() -> None:
create_db_and_tables()
create_hero()
select_hero()
if __name__ == "__main__":
main()
Pydantic 支持 UUID
类型。
对于数据库,SQLModel 内部使用 SQLAlchemy 的 Uuid
类型。
创建带有 UUID 的记录¶
当创建 Hero
记录时,id
字段将 自动填充 一个新的 UUID,因为我们设置了 default_factory=uuid.uuid4
。
由于 uuid.uuid4
将在创建模型实例时被调用,甚至在将其发送到数据库之前,我们可以 立即访问和使用 ID。
并且 相同的 ID (UUID) 将被保存在数据库中。
# Code above omitted 👆
def create_hero():
with Session(engine) as session:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
print("The hero before saving in the DB")
print(hero)
print("The hero ID was already set")
print(hero.id)
session.add(hero)
session.commit()
session.refresh(hero)
print("After saving in the DB")
print(hero)
# Code below omitted 👇
👀 完整文件预览
import uuid
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, 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_hero():
with Session(engine) as session:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
print("The hero before saving in the DB")
print(hero)
print("The hero ID was already set")
print(hero.id)
session.add(hero)
session.commit()
session.refresh(hero)
print("After saving in the DB")
print(hero)
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
statement = select(Hero).where(Hero.id == hero_id)
selected_hero = session.exec(statement).one()
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
def main() -> None:
create_db_and_tables()
create_hero()
select_hero()
if __name__ == "__main__":
main()
🤓 其他版本和变体
import uuid
from typing import Union
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Union[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_hero():
with Session(engine) as session:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
print("The hero before saving in the DB")
print(hero)
print("The hero ID was already set")
print(hero.id)
session.add(hero)
session.commit()
session.refresh(hero)
print("After saving in the DB")
print(hero)
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
statement = select(Hero).where(Hero.id == hero_id)
selected_hero = session.exec(statement).one()
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
def main() -> None:
create_db_and_tables()
create_hero()
select_hero()
if __name__ == "__main__":
main()
选择一个 Hero¶
我们可以执行与其他字段相同的操作。
例如,我们可以 通过 ID 选择一个 hero
# Code above omitted 👆
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
statement = select(Hero).where(Hero.id == hero_id)
selected_hero = session.exec(statement).one()
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
# Code below omitted 👇
👀 完整文件预览
import uuid
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, 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_hero():
with Session(engine) as session:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
print("The hero before saving in the DB")
print(hero)
print("The hero ID was already set")
print(hero.id)
session.add(hero)
session.commit()
session.refresh(hero)
print("After saving in the DB")
print(hero)
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
statement = select(Hero).where(Hero.id == hero_id)
selected_hero = session.exec(statement).one()
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
def main() -> None:
create_db_and_tables()
create_hero()
select_hero()
if __name__ == "__main__":
main()
🤓 其他版本和变体
import uuid
from typing import Union
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Hero(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Union[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_hero():
with Session(engine) as session:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
print("The hero before saving in the DB")
print(hero)
print("The hero ID was already set")
print(hero.id)
session.add(hero)
session.commit()
session.refresh(hero)
print("After saving in the DB")
print(hero)
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
statement = select(Hero).where(Hero.id == hero_id)
selected_hero = session.exec(statement).one()
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
def main() -> None:
create_db_and_tables()
create_hero()
select_hero()
if __name__ == "__main__":
main()
提示
即使像 SQLite 这样的数据库将 UUID 存储为字符串,我们也可以使用 Python UUID 对象选择和运行比较,并且它可以工作。
SQLModel(实际上是 SQLAlchemy)会负责使其正常工作。 ✨
使用 session.get()
选择¶
我们也可以使用 session.get()
按 ID 选择
# Code above omitted 👆
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
selected_hero = session.get(Hero, hero_id)
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
# Code below omitted 👇
👀 完整文件预览
import uuid
from sqlmodel import Field, Session, SQLModel, create_engine
class Hero(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, 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_hero():
with Session(engine) as session:
hero = Hero(name="Deadpond", secret_name="Dive Wilson")
print("The hero before saving in the DB")
print(hero)
print("The hero ID was already set")
print(hero.id)
session.add(hero)
session.commit()
session.refresh(hero)
print("After saving in the DB")
print(hero)
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
selected_hero = session.get(Hero, hero_id)
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
def main() -> None:
create_db_and_tables()
create_hero()
select_hero()
if __name__ == "__main__":
main()
🤓 其他版本和变体
import uuid
from typing import Union
from sqlmodel import Field, Session, SQLModel, create_engine
class Hero(SQLModel, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
name: str = Field(index=True)
secret_name: str
age: Union[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_hero():
with Session(engine) as session:
hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
print("The hero before saving in the DB")
print(hero_1)
print("The hero ID was already set")
print(hero_1.id)
session.add(hero_1)
session.commit()
session.refresh(hero_1)
print("After saving in the DB")
print(hero_1)
def select_hero():
with Session(engine) as session:
hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
session.add(hero_2)
session.commit()
session.refresh(hero_2)
hero_id = hero_2.id
print("Created hero:")
print(hero_2)
print("Created hero ID:")
print(hero_id)
selected_hero = session.get(Hero, hero_id)
print("Selected hero:")
print(selected_hero)
print("Selected hero ID:")
print(selected_hero.id)
def main() -> None:
create_db_and_tables()
create_hero()
select_hero()
if __name__ == "__main__":
main()
与使用其他字段相同的方式,我们可以更新、删除等等。 🚀
运行程序¶
如果您运行该程序,您将看到在 Python 代码中生成的 UUID,然后记录 以相同的 UUID 保存在数据库中。
$ python app.py
// Some boilerplate and previous output omitted 😉
// In SQLite, the UUID will be stored as a string
// other DBs like Postgres have a specific UUID type
CREATE TABLE hero (
id CHAR(32) NOT NULL,
name VARCHAR NOT NULL,
secret_name VARCHAR NOT NULL,
age INTEGER,
PRIMARY KEY (id)
)
// Before saving in the DB we already have the UUID
The hero before saving in the DB
name='Deadpond' secret_name='Dive Wilson' id=UUID('0e44c1a6-88d3-4a35-8b8a-307faa2def28') age=None
The hero ID was already set
0e44c1a6-88d3-4a35-8b8a-307faa2def28
// The SQL statement to insert the record uses our UUID
INSERT INTO hero (id, name, secret_name, age) VALUES (?, ?, ?, ?)
('0e44c1a688d34a358b8a307faa2def28', 'Deadpond', 'Dive Wilson', None)
// And indeed, the record was saved with the UUID we created 😎
After saving in the DB
age=None id=UUID('0e44c1a6-88d3-4a35-8b8a-307faa2def28') name='Deadpond' secret_name='Dive Wilson'
// Now we create a new hero (to select it in a bit)
Created hero:
age=None id=UUID('9d90d186-85db-4eaa-891a-def7b4ae2dab') name='Spider-Boy' secret_name='Pedro Parqueador'
Created hero ID:
9d90d186-85db-4eaa-891a-def7b4ae2dab
// And now we select it
Selected hero:
age=None id=UUID('9d90d186-85db-4eaa-891a-def7b4ae2dab') name='Spider-Boy' secret_name='Pedro Parqueador'
Selected hero ID:
9d90d186-85db-4eaa-891a-def7b4ae2dab
了解更多¶
您可以在以下位置了解更多关于 UUID 的信息
- 官方 Python UUID 文档。
- UUID 维基百科。