跳到内容

UUID(通用唯一标识符)

我们已经讨论了一些数据类型,例如 strint 等。

还有另一种数据类型称为 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),因此两次生成相同的版本 4 UUID(随机的)的概率非常低。

如果您在数据库中存储了 103 万亿个版本 4 UUID,那么生成一个重复的新 UUID 的概率是十亿分之一。🤓

出于同样的原因,如果您决定迁移数据库、将其与另一个数据库合并并混合记录等,您很可能能够 直接使用您最初拥有的相同 UUID

警告

仍然存在发生冲突的可能性,但非常低。在大多数情况下,您可以假设不会发生冲突,但最好为此做好准备。

UUID 防止信息泄露

因为版本 4 的 UUID 是 随机的,您可以将这些 ID 提供给应用程序用户或其他系统,而 不会泄露有关您应用程序的信息

当使用 自增整数 作为主键时,您可能会隐式地泄露有关您系统的信息。例如,某人可以创建一个新英雄,通过获取英雄 ID 20他们就会知道您系统中有 20 个英雄(如果某些英雄已被删除,则可能更少)。

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 字段将因为我们设置了 default_factory=uuid.uuid4自动填充 新的 UUID。

由于 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()

选择一个英雄

我们可以执行与其他字段相同的操作。

例如,我们可以 按 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)

        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 的信息