UUID vs. ULID vs. Nano ID vs. Auto-Increment-IDs

Fünf verbreitete ID-Verfahren im Vergleich, und zwar bei dem, was in der Praxis zählt: Größe, Sortierung, Kollisionsrisiko, Verhalten in der Datenbank und was eine ID preisgibt.

Direkt ausprobieren: UUID-v7-Generator UUID-v4-Generator

Die Wahl eines ID-Formats läuft auf drei Fragen hinaus. Müssen sich IDs nach dem Erstellungszeitpunkt sortieren lassen? Tauchen sie in URLs oder öffentlichen APIs auf? Und speichert die Datenbank sie in einer nativen 128-Bit-Spalte oder als Text? UUID v7, im Mai 2024 in RFC 9562 standardisiert, hat die Antwort für viele Projekte verändert: Sie bietet die zeitliche Sortierung, die ULID populär gemacht hat, in einem Format, das jede UUID-Spalte und jede Bibliothek bereits akzeptiert.

Auf einen Blick

Auto-IncrementUUID v4UUID v7ULIDNano ID
Beispiel1042f47ac10b-58cc-4372-a567-0e02b2c3d47901926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f1001J9QKMZ1BFN79M7SVBJ6JWVRGV1StGXR8_Z5jdHi6B-myT
Binäre Größe4 oder 8 Bytes16 Bytes16 Bytes16 BytesAls Text gespeichert
TextlängeBis zu 10 oder 19 Ziffern363626Standardmäßig 21
ZufallsbitsKeine122Bis zu 7480Standardmäßig 126
Nach Erstellungszeit sortierbarJaNeinJa, auf die MillisekundeJa, auf die MillisekundeNein
Ohne Datenbank erzeugbarNeinJaJaJaJa
SpezifikationDatenbankfunktionRFC 9562RFC 9562Community-SpezifikationBibliothek
VerrätAnzahl und ReihenfolgeNichtsErstellungszeitErstellungszeitNichts

Wie die einzelnen IDs aufgebaut sind

  • Auto-Increment ist ein Zähler, den die Datenbank führt. Ein vorzeichenbehaftetes 32-Bit-INT endet bei 2.147.483.647, ein vorzeichenbehaftetes BIGINT bei 9.223.372.036.854.775.807. Die Anwendung erfährt die ID erst nach dem Insert, es sei denn, sie reserviert vorher Werte aus einer Sequenz.
  • UUID v4 besteht aus 122 Zufallsbits und 6 festen Bits, die Version und Variante kennzeichnen.
  • UUID v7 beginnt mit einem 48-Bit-Unix-Zeitstempel in Millisekunden, gefolgt von der Version, 12 Bits namens rand_a, der Variante und 62 Bits namens rand_b. RFC 9562 erlaubt einem Generator, einen Teil dieser Bits als Zähler zu nutzen, damit IDs aus derselben Millisekunde trotzdem in der richtigen Reihenfolge entstehen. Der Generator auf dieser Website macht das mit den 12 Bits von rand_a.
  • ULID besteht aus einem 48-Bit-Zeitstempel in Millisekunden und 80 Zufallsbits, geschrieben als 26 Zeichen in Crockfords Base32: 10 für die Zeit und 16 für den Zufall. Das Alphabet lässt I, L, O und U weg und unterscheidet nicht zwischen Groß- und Kleinschreibung. Im monotonen Modus übernimmt eine ULID, die in derselben Millisekunde wie die vorige entsteht, deren Zufallsteil und erhöht ihn um eins.
  • Nano ID ist eine Folge zufälliger Zeichen, standardmäßig 21 Stück aus dem 64 Zeichen umfassenden Alphabet A-Z a-z 0-9 _ -. Jedes Zeichen trägt 6 Bits, das ergibt 126 Bits. Länge und Alphabet lassen sich ändern, und die Zahl der Zufallsbits ändert sich entsprechend mit.

Eine ULID und eine UUID sind beide 128-Bit-Zahlen, nur unterschiedlich geschrieben. 01J9QKMZ1BFN79M7SVBJ6JWVRG und 01926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f10 sind derselbe Wert, einmal in Base32 und einmal in Hex. Deshalb lässt sich eine ULID in einer nativen UUID-Spalte speichern, etwa im Typ uuid von PostgreSQL, und behält dort ihre Sortierung. Eine ULID, die nicht als v7-UUID entstanden ist, hat an den Stellen für Version und Variante allerdings beliebige Bits, sodass ein strenger UUID-Validator sie womöglich ablehnt.

Kollisionsrisiko, nachgerechnet

Für rein zufällige IDs gilt die Geburtstagsnäherung: Bei n IDs aus N = 2 hoch Anzahl der Zufallsbits möglichen Werten liegt die Wahrscheinlichkeit für mindestens ein Duplikat bei etwa 1 − e^(−n²/2N) und erreicht 50 Prozent bei ungefähr n = 1,18 × √N.

VerfahrenZufallsbitsIDs für 50% Duplikat-WahrscheinlichkeitDuplikat-Wahrscheinlichkeit bei 1 Milliarde IDs
UUID v41222,7 × 10^189,4 × 10^-20
Nano ID, 21 Zeichen1261,1 × 10^195,9 × 10^-21
Nano ID, 10 Zeichen601,3 × 10^9Etwa 35%

Die letzte Zeile sollten Sie sich merken: Wer eine Nano ID für hübschere URLs kürzt, verliert sehr schnell an Kollisionssicherheit. Rechnen Sie also nach, bevor Sie sich für eine Länge entscheiden.

Bei UUID v7 und ULID können zwei IDs nur kollidieren, wenn sie dieselbe Millisekunde tragen. n ist hier also die Zahl der IDs, die in einer Millisekunde von Generatoren ohne gemeinsamen Zähler erzeugt werden. Bei 1.000 solcher IDs in derselben Millisekunde liegt die Wahrscheinlichkeit für ein Duplikat bei etwa 2,6 × 10^-17 mit 74 Zufallsbits und bei 4,1 × 10^-19 mit den 80 Bits von ULID. In der Praxis ist das Risiko bei allen Verfahren hier eine schwache Zufallsquelle wie Math.random, nicht die Mathematik.

Verhalten im Datenbankindex

Primärschlüssel liegen meist in einem B-Baum-Index. Ist jeder neue Schlüssel größer als der vorige, wie bei Auto-Increment, UUID v7 und ULID, landen Inserts am rechten Rand des Index, Seiten werden vollständig gefüllt, und kürzlich geschriebene Zeilen bleiben nah beieinander. Zufällige Schlüssel, also UUID v4 und Nano ID, landen irgendwo: Seiten werden geteilt, bleiben teilweise leer, und sobald der Index nicht mehr in den Arbeitsspeicher passt, kann jeder Insert einen zufälligen Lesezugriff bedeuten.

Stärker ist der Effekt in MySQL und MariaDB mit InnoDB, die die Zeilen selbst in Primärschlüssel-Reihenfolge speichern. Zufällige Schlüssel verstreuen dort also die ganze Tabelle und nicht nur den Index. PostgreSQL hält die Zeilen in einem separaten Heap, der Unterschied beschränkt sich dort auf den Index.

Das Speicherformat ist genauso wichtig wie das Verfahren:

  • Speichern Sie UUIDs und ULIDs in einem nativen 16-Byte-Typ, wo es einen gibt: uuid in PostgreSQL, UUID in MariaDB ab 10.7, BINARY(16) in MySQL. CHAR(36) braucht mehr als doppelt so viel Platz und vergrößert jeden Index.
  • Übergeben Sie in MySQL bei v7-Werten nicht das Swap-Flag an UUID_TO_BIN(uuid, 1). Es verschiebt die Zeitfelder zugunsten von v1-UUIDs und würde die Sortierung von v7 zerstören.
  • uniqueidentifier in SQL Server vergleicht die letzten sechs Bytes zuerst, deshalb sortieren sich dort gespeicherte v7-Werte nicht nach der Zeit. Die Details stehen in UUID vs. GUID.
  • Nano IDs sind Strings und werden als CHAR oder VARCHAR gespeichert, mit binärer Kollation, falls Groß- und Kleinschreibung eine Rolle spielen muss.

URLs und was eine ID preisgibt

Alle fünf Formate lassen sich ohne Prozentkodierung in einer URL verwenden: Hex-Ziffern, Bindestriche, Crockford-Base32 und das Nano-ID-Alphabet bestehen ausschließlich aus nicht reservierten Zeichen nach RFC 3986. Der Unterschied liegt in der Länge (36, 26 oder 21 Zeichen) und in der Groß- und Kleinschreibung. UUIDs und ULIDs unterscheiden sie nicht und überstehen daher Systeme, die die Schreibung ändern; Nano IDs dagegen nicht.

Was eine ID verrät, gibt oft den Ausschlag:

  • Auto-Increment verrät Mengen. Hat Ihre Rechnung heute die Nummer 1042 und eine Woche später 1342, hat das Unternehmen in dieser Woche rund 300 Rechnungen ausgestellt. Fortlaufende IDs machen es außerdem trivial, benachbarte Datensätze auszuprobieren, wodurch jede fehlende Zugriffsprüfung zum Datenleck wird.
  • UUID v7 und ULID verraten, wann der Datensatz erstellt wurde, und zwar auf die Millisekunde genau. Bei Bestellungen ist das selten ein Problem, bei Benutzerkonten oder medizinischen Daten kann es eines sein. Fügen Sie eine v7-UUID in den Prüfbereich des UUID-v7-Generators ein, und er zeigt den Zeitstempel an.
  • UUID v4 und Nano ID verraten nichts darüber, wann oder wo sie erzeugt wurden.

Keine dieser IDs ist ein Geheimnis. Eine nicht erratbare ID erschwert das Ausprobieren, ersetzt aber nicht die Prüfung, ob der Nutzer den Datensatz sehen darf.

Welche ID Sie wählen sollten

  • Interne Tabellen in einer einzigen Datenbank, die Nutzer nie zu sehen bekommen: Auto-Increment BIGINT. Das ist der kleinste und einfachste Schlüssel.
  • Primärschlüssel, die von der Anwendung oder über mehrere Dienste hinweg erzeugt werden, sofern eine native UUID-Spalte verfügbar ist: UUID v7.
  • Öffentliche Kennungen, bei denen die Erstellungszeit privat bleiben muss, Korrelations-IDs und Dateinamen: UUID v4, aus dem UUID-v4-Generator oder von crypto.randomUUID().
  • Kurze IDs in URLs, bei denen die Reihenfolge keine Rolle spielt: Nano ID in Standardlänge oder nahe daran.
  • ULID, wenn Ihr Stack sie bereits verwendet oder Sie die kürzere Textform ohne Unterscheidung der Groß- und Kleinschreibung möchten. Für neue Projekte deckt UUID v7 dasselbe in einem Standardformat ab.

Eine verbreitete Kombination vereint das Beste aus beiden Welten: ein Primärschlüssel als BIGINT oder UUID v7 für Joins und Indizes, dazu eine separate zufällige öffentliche ID mit eindeutigem Index für URLs und APIs.

Häufige Fragen

Ist ULID besser als UUID v7?

Beide sind gleich aufgebaut: ein 48-Bit-Zeitstempel in Millisekunden, gefolgt von Zufallsbits, insgesamt 128 Bits. UUID v7 ist ein IETF-Standard, der in UUID-Spalten und Validatoren passt. ULID hat eine kürzere Textform ohne Unterscheidung der Groß- und Kleinschreibung und 80 Zufallsbits statt bis zu 74.

Ist eine Nano ID so eindeutig wie eine UUID?

In der Standardlänge von 21 Zeichen ja: Sie hat 126 Zufallsbits gegenüber 122 bei UUID v4. Kürzere Nano IDs verlieren diesen Vorteil schnell; bei 10 Zeichen liegt die Wahrscheinlichkeit für ein Duplikat schon nach etwa 1,3 Milliarden IDs bei 50 Prozent.

Können UUID-v7-Werte kollidieren?

Kollidieren können nur IDs, die in derselben Millisekunde von unabhängigen Generatoren erzeugt werden. Bei 1.000 davon in einer Millisekunde liegt die Wahrscheinlichkeit bei etwa 2,6 × 10^-17. Eine solide Zufallsquelle ist also weit wichtiger als das Format.

Warum sind zufällige UUIDs als Primärschlüssel langsam?

Jeder neue Schlüssel landet an einer zufälligen Stelle im B-Baum-Index. Das führt zu Seitenteilungen, halb leeren Seiten und bei großen Tabellen zu mehr Lesezugriffen auf die Festplatte. Zeitlich sortierte IDs wie UUID v7 oder ULID werden stattdessen am Ende angehängt.
Direkt ausprobieren: UUID-v7-Generator UUID-v4-Generator