Zum Inhalt springen

UUID (Universell Eindeutige Identifikatoren)

Wir haben einige Datentypen wie str, int usw. besprochen.

Es gibt einen weiteren Datentyp namens UUID (Universally Unique Identifier).

Sie haben UUIDs vielleicht schon gesehen, zum Beispiel in URLs. Sie sehen ungefähr so aus

4ff2dab7-bffe-414d-88a5-1826b9fea8df

UUIDs können besonders nützlich sein als Alternative zu automatisch inkrementierenden Ganzzahlen für Primärschlüssel.

Info

Offizielle Unterstützung für UUIDs wurde in SQLModel Version 0.0.20 hinzugefügt.

Über UUIDs

UUIDs sind Zahlen mit 128 Bits, also 16 Bytes.

Sie werden normalerweise als 32 hexadezimale Zeichen gesehen, die durch Bindestriche getrennt sind.

Es gibt mehrere Versionen von UUIDs, einige Versionen enthalten die aktuelle Zeit in den Bytes, aber UUIDs Version 4 sind hauptsächlich zufällig. Die Art und Weise, wie sie generiert werden, macht sie praktisch eindeutig.

Verteilte UUIDs

Sie könnten eine UUID auf einem Computer generieren, und jemand anderes könnte eine andere UUID auf einem anderen Computer generieren, und es wäre fast unmöglich, dass beide UUIDs gleich sind.

Das bedeutet, dass Sie nicht darauf warten müssen, dass die DB die ID für Sie generiert. Sie können sie im Code generieren, bevor Sie sie an die Datenbank senden, da Sie ziemlich sicher sein können, dass sie eindeutig sein wird.

Technische Details

Da die Anzahl der möglichen UUIDs so groß ist (2^128), ist die Wahrscheinlichkeit, dieselbe UUID Version 4 (die zufälligen) zweimal zu generieren, sehr gering.

Wenn Sie 103 Billionen UUIDs der Version 4 in der Datenbank gespeichert hätten, wäre die Wahrscheinlichkeit, eine duplizierte neue zu generieren, eins zu einer Milliarde. 🤓

Aus demselben Grund könnten Sie, wenn Sie beschließen, Ihre Datenbank zu migrieren, sie mit einer anderen Datenbank zu kombinieren und Datensätze zu mischen usw., höchstwahrscheinlich einfach dieselben UUIDs verwenden, die Sie ursprünglich hatten.

Warnung

Es besteht immer noch die Möglichkeit einer Kollision, aber sie ist sehr gering. In den meisten Fällen könnten Sie davon ausgehen, dass keine Kollision auftritt, aber es wäre gut, darauf vorbereitet zu sein.

UUIDs verhindern Informationslecks

Da UUIDs Version 4 zufällig sind, könnten Sie diese IDs den Anwendern der Anwendung oder anderen Systemen geben, ohne Informationen über Ihre Anwendung preiszugeben.

Wenn Sie automatisch inkrementierende Ganzzahlen für Primärschlüssel verwenden, könnten Sie implizit Informationen über Ihr System preisgeben. Zum Beispiel könnte jemand einen neuen Helden erstellen und durch Erhalt der Helden-ID 20 wüsste er, dass Sie 20 Helden in Ihrem System haben (oder sogar weniger, wenn einige Helden bereits gelöscht wurden).

UUID-Speicherung

Da UUIDs 16 Bytes groß sind, würden sie mehr Speicherplatz in der Datenbank verbrauchen als eine kleinere automatisch inkrementierende Ganzzahl (normalerweise 4 Bytes).

Abhängig von der von Ihnen verwendeten Datenbank können UUIDs eine bessere oder schlechtere Leistung aufweisen. Wenn Sie sich darum sorgen, sollten Sie die Dokumentation für die spezifische Datenbank prüfen.

SQLite hat keinen spezifischen UUID-Typ, daher wird die UUID als Zeichenkette gespeichert. Andere Datenbanken wie Postgres haben einen spezifischen UUID-Typ, der zu einer besseren Leistung und Speichernutzung als Zeichenketten führen würde.

Modelle mit UUIDs

Um UUIDs als Primärschlüssel zu verwenden, müssen wir uuid importieren, was Teil der Standardbibliothek von Python ist (wir müssen nichts installieren) und uuid.UUID als Typ für das ID-Feld verwenden.

Wir möchten auch, dass der Python-Code eine neue UUID generiert, wenn eine neue Instanz erstellt wird, daher verwenden wir default_factory.

Der Parameter default_factory nimmt eine Funktion (oder im Allgemeinen ein "Callable"). Diese Funktion wird beim Erstellen einer neuen Instanz des Modells aufgerufen und der von der Funktion zurückgegebene Wert wird als Standardwert für das Feld verwendet.

Für die Funktion in default_factory übergeben wir uuid.uuid4, was eine Funktion ist, die eine neue UUID Version 4 generiert.

Tipp

Wir rufen uuid.uuid4() nicht selbst im Code auf (wir setzen keine Klammern). Stattdessen übergeben wir die Funktion selbst, nur uuid.uuid4, damit SQLModel sie jedes Mal aufrufen kann, wenn wir eine neue Instanz erstellen.

Das bedeutet, dass die UUID im Python-Code generiert wird, bevor die Daten an die Datenbank gesendet werden.

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 👇
👀 Vorschau der vollständigen Datei
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()
🤓 Andere Versionen und Varianten
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 hat Unterstützung für UUID Typen.

Für die Datenbank verwendet SQLModel intern SQLAlchemy's Uuid Typ.

Einen Datensatz mit einer UUID erstellen

Beim Erstellen eines Hero-Datensatzes wird das Feld id automatisch mit einer neuen UUID gefüllt, da wir default_factory=uuid.uuid4 gesetzt haben.

Da uuid.uuid4 beim Erstellen der Modellinstanz aufgerufen wird, noch bevor sie an die Datenbank gesendet wird, können wir auf die ID sofort zugreifen und sie verwenden.

Und dieselbe ID (eine UUID) wird in der Datenbank gespeichert.

# 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 👇
👀 Vorschau der vollständigen Datei
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()
🤓 Andere Versionen und Varianten
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()

Einen Helden auswählen

Wir können dieselben Operationen durchführen, die wir mit anderen Feldern durchführen konnten.

Zum Beispiel können wir einen Helden nach ID auswählen

# 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 👇
👀 Vorschau der vollständigen Datei
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()
🤓 Andere Versionen und Varianten
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()

Tipp

Selbst wenn eine Datenbank wie SQLite die UUID als Zeichenkette speichert, können wir Auswahl- und Vergleichsvorgänge mit einem Python UUID-Objekt durchführen, und es wird funktionieren.

SQLModel (tatsächlich SQLAlchemy) wird dafür sorgen, dass es funktioniert. ✨

Auswahl mit session.get()

Wir könnten die ID auch mit session.get() auswählen

# 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 👇
👀 Vorschau der vollständigen Datei
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()
🤓 Andere Versionen und Varianten
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()

Auf die gleiche Weise wie bei anderen Feldern könnten wir aktualisieren, löschen usw. 🚀

Das Programm ausführen

Wenn Sie das Programm ausführen, sehen Sie die UUID, die im Python-Code generiert wurde, und dann den Datensatz, der mit derselben UUID in der Datenbank gespeichert wurde.

$ 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

Mehr erfahren

Sie können mehr über UUIDs erfahren unter