UUID v1 vs. v4 vs. v7: Einen DB-Primärschlüssel wählen

Veröffentlicht am 2026-04-13 8 Min. Lesezeit

Zusammenfassung (TL;DR)

Klartext: UUID v4 als Primärschlüssel einer insert-lastigen Tabelle zu verwenden, ist ein Eigentor. Ich habe mir kürzlich ein PostgreSQL-16-Workload angesehen, bei dem events.id standardmäßig gen_random_uuid() (ein v4) war, und jeder INSERT landete auf einem zufälligen B-Tree-Blatt – kühlte den shared_buffers aus und trieb die laut pgstattuple gemeldete Indexfragmentierung in den hohen einstelligen Prozentbereich. Den Spalten-Default auf einen v7-Generator umzustellen – derselbe 16-Byte-uuid-Typ, derselbe Index – kürzte die durchschnittliche INSERT-Latenz auf rund ein Drittel des vorherigen Werts, und die Fragmentierungswerte stabilisierten sich. Eine UUID-Version zu wählen ist eine Datenbankdesign-Entscheidung, keine Kryptografie-Entscheidung. Eine UUID ist ein 128-Bit-Wert, geschrieben als 8-4-4-4-12 Hex-Ziffern, mit vier Bits als Version und zwei oder drei Bits als Variant festgeschrieben. Version 1 stempelt Zeit und eine Node-ID (historisch eine MAC-Adresse) in diese 128 Bit – grob sortierbar, verrät aber den Host. Version 4 füllt die nicht reservierten Bits mit Zufallsdaten: starke Unerratbarkeit, aber keine Ordnung, sodass Inserts an beliebigen Indexstellen landen. Version 7, in RFC 9562 (2024) formalisiert, legt einen Unix-Millisekunden-Zeitstempel in die höchsten Bits und hängt einen Zufallsschwanz an, was die Sicherheit von v4 mit der Indexlokalität von v1 kombiniert. Für eine öffentliche API, in der opake Zufälligkeit wirklich zählt, ist v4 weiterhin der Standard. Fast überall sonst ist v7 der bessere Primärschlüssel. v1 ist Legacy – von Datenbanken akzeptiert, sollte aber keine neue Wahl mehr sein.

Hintergrund und Konzepte

Eine UUID hat 128 Bit. Nach Konvention wird sie als 32 Hex-Ziffern mit Bindestrichen gedruckt: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx. Die M-Ziffer trägt die Version (1, 4, 7 usw.) und die höchsten Bits von N tragen die Variant. Alles Übrige ist versionsspezifisch.

Version 1 wurde für Eindeutigkeit über Maschinen und Zeit hinweg entworfen. Sein 60-Bit-Zeitstempel zählt 100-Nanosekunden-Intervalle seit 1582-10-15, ein Clock-Sequence-Feld behandelt Uhrenrücksprünge, und eine 48-Bit-Node-ID war ursprünglich die Netzwerk-MAC-Adresse. Die Datenschutzkonsequenz ist direkt: Eine auf deinem Laptop geprägte v1-UUID codiert dessen MAC, und ein einzeiliges uuid -d kehrt sie um. Moderne Bibliotheken randomisieren die Node-ID manchmal, um das Leak zu vermeiden, doch viele folgen weiterhin der Originalregel.

Version 4 besteht aus 122 Bit Zufall mit 6 festgelegten Bits für Version und Variant. Sie setzt einen starken RNG voraus (crypto.randomUUID() im Browser oder gen_random_uuid() in PostgreSQL). Kollisionen sind in realistischer Skala effektiv ausgeschlossen – die Birthday-Bound gibt nach rund einer Milliarde erzeugter v4s etwa eine Billionstel Wahrscheinlichkeit. Der Nachteil ist, dass aufeinanderfolgende v4s unkorreliert sind; sie in einen Index einzufügen, berührt zufällige Seiten, was Cache-Lokalität und Write Amplification schadet (WAL plus Full-Page-Writes in Postgres).

Version 7 ist Teil von RFC 9562, veröffentlicht 2024, der RFC 4122 ablöste und die Versionen 6, 7 und 8 hinzufügte. Eine v7-UUID speichert einen 48-Bit-Unix-Millisekunden-Zeitstempel in ihren höchsten Bits, gefolgt vom Versions-Tag, einem kleinen rand_a-Feld, dem Variant-Tag und einem 62-Bit-rand_b-Schwanz. Praktisch bedeutet das, dass v7-Werte, die innerhalb derselben Millisekunde erzeugt werden, in der Sortierreihenfolge benachbart sind, und Werte über Millisekunden hinweg chronologisch sortieren. Der Zufallsschwanz liefert weiterhin weit mehr Entropie, als für Eindeutigkeit innerhalb einer Millisekunde nötig ist. PostgreSQL 18 bringt ein natives uuidv7(); auf früheren Versionen kannst du die Erweiterung pg_uuidv7 nutzen oder sie auf Applikationsebene erzeugen (Node uuid 9.x, Python uuid6).

Die Variant-Bits sind wichtig, weil sie die RFC-9562-/4122-Familie von Legacy-Microsoft- und -Apollo-UUIDs unterscheiden. Für diesen Guide gehe von der RFC-Variant aus (die erste Hex-Ziffer der N-Gruppe ist 8, 9, a oder b).

Das Speicherformat ist ein eigener Aspekt. PostgreSQL 16s nativer uuid-Typ speichert den Wert als 16 Bytes; MySQL verwendet typischerweise BINARY(16) oder CHAR(36) – die Stringform verdoppelt den Speicherbedarf und macht Vergleiche zeichenweise statt byteweise. Versionswahl und Speicherform interagieren: v7 binär zu sortieren ist billig und korrekt, als Hex-String zu sortieren ist korrekt, aber langsamer, und v4 zu sortieren ist unabhängig von der Speicherform bedeutungslos.

Vergleich und Daten

Eigenschaftv1v4v7
ErzeugungseingabeZeitstempel + Clock Sequence + Node-ID122 Zufallsbits48-Bit-Unix-ms-Zeitstempel + Zufallsschwanz
DatenschutzLeakt Node-ID (oft die MAC)Keine Host- oder ZeitinformationLeakt Erstellungszeit in ms, kein Host
ZeitsortierbarJa (aber Byte-Reihenfolge ≠ Zeit-Reihenfolge ohne Umstellung)NeinJa — lexikografische Reihenfolge deckt sich mit chronologischer
IndexlokalitätMittelSchlecht (zufällige Inserts quer durch den B-Tree)Gut (nahezu monoton)
Typischer EinsatzLegacy-Systeme, manche COM-/Windows-IDsÖffentliche API-IDs, Session-Tokens, SaltsEvent-Logs, insert-lastige Workloads, zeitpaginierte Tabellen
EntropieNiedrig (meiste Bits sind Zeit/Node)Hoch (rund 122 Bit)Hoher Schwanz (rund 74 Bit), geringe Kollision innerhalb ms

Ein grobes mentales Modell: v4 maximiert Unerratbarkeit auf Kosten des Indexverhaltens, v7 behält für die meisten Anwendungen genügend Unerratbarkeit und stellt das „am Ende anhängen”-Muster wieder her, das Datenbanken lieben, und v1 ist ein historisches Artefakt, das man erkennen, aber nicht wählen sollte.

Praxisszenarien

Szenario 1 – Append-lastiges Event-Log. Das ist genau die Workload aus der Eingangsanekdote. Eine Tabelle, die Millionen Zeilen pro Tag aufnimmt und meist als „letzte 24 Stunden, nach Zeit sortiert” abgefragt wird, profitiert direkt von v7. Neue Zeilen landen am Ende des Primärschlüssel-Index, die heißen Seiten bleiben warm, und Range-Scans über Zeiträume bilden sich auf zusammenhängende Indexsegmente ab. Der Wechsel von v4 zu v7 reduziert hier häufig Schreibnieder und Indexfragmentierung, ohne dass irgendein Query-Code geändert werden muss – meist eine einzeilige Spalten-Default-Änderung.

Szenario 2 – Öffentlich nutzerseitige IDs. Share-Links wie /orders/{id} müssen unerratbar sein, damit Besucher:innen nicht Bestellungen anderer aufzählen können. v4 ist der sichere Standard. Willst du zusätzlich die Vorteile von v7, beachte, dass ein v7 seinen Erstellungszeitstempel millisekundengenau offenlegt, was bei Bestellungen in Ordnung sein mag, aber in sensibleren Kontexten Geschäftssignale (z. B. genauer Checkout-Durchsatz pro Minute) preisgeben kann. Der Kompromiss, den ich Teams empfohlen habe, ist ein Dual-ID-Muster: v7 intern als Primärschlüssel, und eine separate v4 oder ein kurzer Zufalls-Slug, der nach außen sichtbar ist. Du behältst das Indexverhalten, ohne Timing extern zu leaken.

Szenario 3 – Multi-Region- oder Sharded-Systeme. Das Zeitstempel-Präfix von v7 bedeutet, dass zwei Regionen, die in derselben Millisekunde UUIDs erzeugen, zeitlich sauber ineinander verschränkt werden; innerhalb einer Millisekunde gibt es jedoch keine regionsübergreifende Ordnungsgarantie. Brauchst du striktere regionenübergreifende Ordnung, hat ULID (ein 48-Bit-Zeitstempel + 80-Bit-Zufall, codiert als Crockford Base32) nahezu identische Eigenschaften und eine kompaktere 26-zeichige textuelle Form. Snowflake-artige IDs gehen weiter, indem sie eine explizite Maschinen-ID für Per-Node-Ordnung enthalten (das ursprüngliche Twitter-Design und die Discord-Variante sind beide 64 Bit), zum Preis der Koordination, um diese IDs zu vergeben.

Häufige Missverständnisse

„UUIDs sind in Datenbanken immer langsam.” Sie sind in Rohwerten langsamer als ein 4-Byte-int, doch die reale Last entsteht durch Random-Insert-Fragmentierung in B-Tree-Indizes, und die eliminiert v7 weitgehend. UUIDs als 16 Bytes statt als 36-Zeichen-String zu speichern, halbiert die Indexgröße und beschleunigt Vergleiche. Viele „UUIDs sind langsam”-Benchmarks sind in Wahrheit „v4 als CHAR(36) in MySQL ist langsam”.

„v4 ist die einzige sichere Version.” v7s Zufallsschwanz ist weiterhin ein großer Entropie-Pool, und für die meisten Anwendungen – Session-Referenzen, API-IDs – ist er unerratbar genug, dass Angreifer:innen sie nicht durchzählen würden. Wo Unerratbarkeit wirklich zählt, ist das Zeitstempel-Präfix: v7 verrät, wann die Zeile erzeugt wurde. Wenn das akzeptabel ist (was es meist ist), ist v7 auch für externe IDs eine vernünftige Wahl.

„UUIDs müssen als Strings gespeichert werden.” Die Stringform hat 36 Zeichen (32 Hex plus 4 Bindestriche) gegenüber 16 Byte binär. Binär ist kompakter und sortiert byteweise korrekt, was für v1 mit seiner nicht-monotonen Byte-Reihenfolge und für v7, bei dem die Byte-Reihenfolge mit der Zeit übereinstimmen soll, wichtig ist. PostgreSQLs uuid-Typ speichert den Wert bereits als 16 Bytes, sodass dort nichts weiter zu überlegen ist.

„v1s MAC-Leak spielt keine Rolle, weil niemand hinschaut.” Die MAC in einer v1-UUID ist eine bekannte Umkehrung – uuid -d und jedes Forensik-Tool extrahieren sie. Erscheinen deine UUIDs in URLs, Support-Tickets oder mit Dritten geteilten Log-Dumps, ist das eine reale Informationspreisgabe.

Checkliste

  1. Erscheint diese UUID in einem Datenbankindex mit häufigen Inserts? Bevorzuge v7. Fallback auf v4 nur, wenn die Unerratbarkeit der Erstellungszeit zwingend ist.
  2. Ist die UUID für Endnutzer:innen oder Partner sichtbar? Beide Versionen funktionieren; prüfe nur, ob der Zeitstempel-Leak von v7 im Kontext akzeptabel ist.
  3. Bist du auf Postgres? Speichere als uuid (16 Bytes). In MySQL nutze BINARY(16), sofern String-Kompatibilität nicht die Größenkosten rechtfertigt.
  4. Brauchst du Ordnungsgarantien über mehrere Generatoren hinweg? v7 allein reicht nicht; ziehe ULID mit demselben Zeit-Präfix oder eine Snowflake-artige ID mit expliziter Maschinen-ID in Betracht.
  5. Steckt v1 noch in der Codebasis? Dokumentiere den MAC-Leak und plane eine Migration, sobald das Schema es erlaubt.
  6. Erzeugst du UUIDs clientseitig? Nutze eine Bibliothek, die einen kryptografisch starken RNG ansteuert (crypto.randomUUID() in modernen Browsern für v4; v7-Bibliotheken umhüllen typischerweise denselben RNG).

Verwandtes Tool

Der UUID-Generator von Patrache Studio erzeugt v4- und v7-UUIDs lokal, sodass die generierten Werte nie in einem Drittanbieter-Dienst protokolliert werden. UUIDs reisen fast immer in JSON-Payloads – JSON formatieren, validieren und mit Schema prüfen in der Praxis behandelt die Schema-Muster, die diese IDs bei der Wanderung zwischen Diensten wohltypisiert halten. Wenn du eine kompakte textuelle Form brauchst – etwa eine 22-zeichige Kurz-ID aus einer 16-Byte-UUID – erklären die Regeln in Base64 und URL-Kodierung: Zweck, Fallen, richtiger Einsatz, warum Base64URL statt Standard-Base64 die richtige Wahl ist.

Quellen