UUID als Primärschlüssel: Speicherung in PostgreSQL, MySQL und SQLite
16 Byte binär oder 36 Zeichen Text? Welche Spaltentypen die großen Datenbanken für UUIDs bieten, was sie kosten und wie man Indizes davor schützt, zu zerfasern.
Eine UUID als Primärschlüssel ist eine Entscheidung mit langem Schatten. Sie steht in jeder Fremdschlüsselspalte, in jedem Index und in jedem Join. Wer die Speicherform am Anfang richtig wählt, spart sich später eine Migration, die niemand gerne macht.
Der Spaltentyp entscheidet
| Datenbank | Empfohlener Typ | Größe | Anmerkung |
|---|---|---|---|
| PostgreSQL | uuid | 16 Byte | Nativ, mit Index- und Vergleichsoperatoren |
| MySQL 8 | BINARY(16) | 16 Byte | Umwandlung mit UUID_TO_BIN und BIN_TO_UUID |
| MariaDB 10.7+ | UUID | 16 Byte | Nativ, speichert intern umsortiert |
| SQLite | BLOB | 16 Byte | Umwandlung in der Anwendung |
| SQL Server | UNIQUEIDENTIFIER | 16 Byte | Sortiert nach eigener Byte-Reihenfolge |
Die Textform mit 36 Zeichen ist bequem beim Debuggen und teuer im Betrieb: mehr als doppelter Platz, langsamere Vergleiche und ein Index, der pro Eintrag 20 Byte mehr trägt. Bei zehn Millionen Zeilen mit drei Indizes summiert sich das auf mehrere hundert Megabyte, die nur aus Bindestrichen und Hexadezimaldarstellung bestehen.
Warum Version 7 den Index rettet
B-Baum-Indizes mögen Schlüssel, die in der Reihenfolge ihres Entstehens wachsen. Laufende Nummern erfüllen das von selbst. Zufällige v4-UUIDs tun das Gegenteil: Jede Einfügung trifft eine zufällige Blattseite. Die Folgen sind Seitenteilungen, halb leere Seiten und ein Arbeitsspeicher, der ständig andere Teile des Index laden muss.
Version 7 beginnt mit einem Zeitstempel. Neue Werte landen damit immer am rechten Rand, der Index bleibt dicht und die Einfügerate stabil. In Messungen mit PostgreSQL liegt der Unterschied bei großen Tabellen typischerweise zwischen Faktor zwei und fünf beim Einfügen, und der Index ist deutlich kleiner. Wer heute ein Schema anlegt, hat kaum einen Grund, für interne Schlüssel noch Version 4 zu nehmen. Was dagegen spricht, steht im Artikel UUID v4 oder v7?.
PostgreSQL
CREATE TABLE bestellung (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
erstellt_am timestamptz NOT NULL DEFAULT now()
);
gen_random_uuid() liefert Version 4 und ist seit PostgreSQL 13 ohne Erweiterung verfügbar. Ab PostgreSQL 18 gibt es uuidv7(). Auf älteren Versionen erzeugt die Anwendung die v7-Werte und übergibt sie beim Einfügen. Ein Vergleich WHERE id = '…' nutzt den Index wie bei jeder anderen Spalte.
MySQL
CREATE TABLE bestellung (
id BINARY(16) PRIMARY KEY,
erstellt_am DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
);
INSERT INTO bestellung (id) VALUES (UUID_TO_BIN(?));
SELECT BIN_TO_UUID(id) FROM bestellung;
MySQLs eigene Funktion UUID() liefert Version 1. Das zweite Argument von UUID_TO_BIN(uuid, 1) sortiert deren Zeitanteil nach vorne und macht sie indexfreundlich. Bei Version 7 ist das nicht nötig, weil die Zeit ohnehin vorne steht.
SQLite
SQLite kennt keinen UUID-Typ und keine Erzeugungsfunktion. Man speichert 16 Byte als BLOB und wandelt in der Anwendung um. Für Textform-Vergleiche in Abfragen hilft hex(id). Wer viele Zeilen hat, sollte den BLOB-Weg gehen, auch wenn TEXT verlockend einfach ist.
Drei Regeln für den Alltag
- Binär speichern, Text nur anzeigen. Die Umwandlung gehört an den Rand der Anwendung, nicht in jede Abfrage.
- Version 7 für interne Schlüssel. Sortierbar, indexfreundlich, ohne eigene Erzeugungsspalte.
- Version 4 für alles Öffentliche. Sobald eine Kennung in einer URL steht, soll sie nichts über Zeit und Reihenfolge verraten.
Die Werte für Tests, Seeds und Fixtures erzeugt der Generator auf der Startseite, bis zu 500 auf einmal, als Liste oder Textdatei.
Häufige Fragen
Soll ich UUIDs als Text speichern?
Nur wenn die Datenbank keinen binären Typ hat. Text braucht 36 statt 16 Byte, vergleicht langsamer und bläht jeden Index auf, der die Spalte enthält.
Ist eine laufende Nummer nicht schneller?
Auf einem einzelnen Server ja. Sobald mehrere Dienste oder Clients Datensätze erzeugen, bevor sie die Datenbank erreichen, ist eine UUID einfacher als jede Vergabestelle.
Wie erzeuge ich UUIDs in der Datenbank selbst?
PostgreSQL: gen_random_uuid() für v4, ab Version 18 uuidv7(). MySQL: UUID() liefert v1, für v4 nutzt man UUID_TO_BIN(UUID()) mit Swap-Flag oder die Anwendung. SQLite hat keine eingebaute Funktion.
Genug gelesen? Erzeuge deine UUIDs.
Zum Generator