UUID vs. ULID vs. NanoID: Ein ID-Schema im Jahr 2026 wählen
Praktischer Vergleich von UUIDv4, UUIDv7, ULID und NanoID: Sortierbarkeit, Index-Performance, Größe, Kollisionswahrscheinlichkeit und ein klarer Entscheidungsleitfaden.
Ich habe vor zwei Jahren eine 400-Millionen-Zeilen-Tabelle von UUIDv4-Primärschlüsseln migriert, und der Schreib-Durchsatz auf dieser Tabelle verdoppelte sich sofort, ohne den Anwendungscode anzufassen. Diese Erfahrung ist der Grund, warum ich hier Meinungen habe. Die Wahl eines ID-Schemas sieht wie eine Fünf-Minuten-Entscheidung aus und besteuert dann still jeden Insert für die Lebensdauer des Systems. Das ist ein Durchgang durch die echten Optionen im Jahr 2026 und wann jede von ihnen ihren Platz verdient.
Warum nicht einfach auto-inkrement-Integer verwenden
Der Standard in vielen älteren Schemata ist BIGINT AUTO_INCREMENT oder Postgres’ SERIAL. Sie sind klein (8 Bytes), sortieren natürlich und ein B-Baum liebt sie, weil jede neue Zeile an den rechten Rand des Index angehängt wird. Warum greift also irgendjemand nach etwas anderem?
Zwei Gründe, und beide sind real.
Der erste ist das Informationsleck. Eine sequentielle ID sagt jedem, der sie sieht, ungefähr, wie viele Datensätze Sie haben und wie schnell Sie wachsen. Wenn /orders/10432 existiert, ist /orders/10433 Ihre nächste Bestellung, und ein Konkurrent kann den Endpunkt abfragen, um Ihr tägliches Volumen zu zählen. Das ist das klassische “Deutsches Tanker-Problem”, angewendet auf Ihre Geschäftsmetriken. Es macht auch Enumeration-Angriffe trivial: Wenn Ihre Autorisierung eine Lücke hat, geht ein Angreifer den gesamten ID-Bereich durch, indem er von 1 aufwärts zählt.
Der zweite ist die verteilte Generierung. Eine einzelne Sequenz ist ein einzelner Koordinationspunkt. In dem Moment, in dem Sie sharden, oder wenn ein Client eine ID generieren möchte, bevor die Zeile die Datenbank trifft, oder wenn Sie zwei Datenbanken zusammenführen, wird ein globaler Zähler zu einem Engpass oder einem direkten Konflikt. Sie können eine Auto-Inkrement-ID nicht offline prägen.
Die Frage ist also selten “Integer oder etwas Ausgefallenes”. Es ist “welches nicht-sequentielle Schema kostet mich am wenigsten”.
Was eine UUID tatsächlich ist
Eine UUID ist 128 Bit. Das ist die ganze Geschichte für die Speicherung: 16 Bytes roh oder 36 Zeichen in der kanonischen mit Bindestrichen versehenen Textform wie f47ac10b-58cc-4372-a567-0e02b2c3d479.
In diesen 128 Bit gibt es ein 4-Bit-Versionsfeld und ein 2-Bit-Variantenfeld. Das Versionsnibble ist das 13. Hex-Zeichen (die erste Ziffer der dritten Gruppe). Im obigen Beispiel ist es 4, also ist das eine UUIDv4. Sie können die Version einer beliebigen UUID mit dem UUID-Versions-Detektor bestätigen, wenn Sie Daten debuggen, die Sie nicht selbst generiert haben.
Die in der Praxis relevanten Versionen:
- v4: zufällig. 122 der 128 Bits sind zufällig (6 Bits gehen an Version und Variante). Das war der Standard, den jeder ein Jahrzehnt lang verwendet hat.
- v7: zeitgeordnet. Die ersten 48 Bits sind ein Unix-Millisekunden-Zeitstempel, der Rest ist zufällig. Neu in RFC 9562 (2024).
- v1: Zeitstempel plus MAC-Adresse. Größtenteils historisch, und das MAC-Adress-Leck ist ein Datenschutzproblem, das niemand jetzt möchte.
Wenn Sie beide Arten generieren möchten, um sie anzuschauen, erzeugt der UUID-Generator v4 und v7.
Das v4-Problem, das niemand erwähnt
UUIDv4 ist in Ordnung, bis es Ihr Primärschlüssel auf einer großen Tabelle wird. Dann ruiniert es Sie langsam.
Das Problem ist die Index-Lokalität. Ein B-Baum (Postgres) oder ein gruppierter Index (MySQL InnoDB speichert die Zeilendaten physisch nach Primärschlüssel geordnet) möchte, dass neue Schlüssel nahe an aktuellen Schlüsseln landen, damit der aktive Teil des Index im Speicher bleibt und Schreiboperationen sauber ans Ende angehängt werden. UUIDv4 ist gleichmäßig zufällig, sodass jeder Insert an einer zufälligen Position im Index landet. Auf einer großen Tabelle bedeutet das:
- Zufällige Seitenlesungen, um den Einfügepunkt zu finden, weil die relevante Index-Seite wahrscheinlich nicht im Buffer-Pool ist.
- Seitentrennungen überall im Baum anstatt saubere Anhängungen am Ende.
- Ein Arbeitssatz, der effektiv der gesamte Index ist, sodass Ihre Cache-Trefferrate abflacht, wenn die Tabelle über den RAM hinaus wächst.
Auf der Tabelle, die ich migriert habe, war das Symptom eine Insert-Latenz, die mit dem Wachsen der Tabelle zunahm, ohne dass sich die Abfrage geändert hatte, um das zu erklären. Die Lösung war, die Primärschlüsselgenerierung von v4 auf v7 umzustellen. Gleicher Spaltentyp (uuid), gleiche 16 Bytes, keine Migration vorhandener Zeilen erforderlich. Inserts gingen wieder dazu über, nahe am rechten Rand anzuhängen, weil v7-Werte mit der Zeit zunehmen. Der Schreib-Durchsatz verdoppelte sich etwa, und der Buffer-Pool hörte auf zu thrashing.
-- Postgres 18 liefert das eingebaut:
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT uuidv7(),
total numeric NOT NULL
);
Wenn Sie auf einem älteren Postgres feststecken, generieren Sie v7 in der Anwendungsschicht oder verwenden Sie die pg_uuidv7-Erweiterung. Der Punkt ist derselbe: Die Spalte ändert sich nicht, nur der Generator tut es.
Ein Vorbehalt. v7 bettet einen Millisekunden-Zeitstempel im Klartext ein, sodass jeder, der die ID hält, die Erstellungszeit lesen kann. Für einen internen Primärschlüssel ist das normalerweise in Ordnung. Für einen öffentlich sichtbaren Bezeichner in einer URL, entscheiden Sie, ob Sie es in Ordnung finden, das zu leaken.
ULID
ULID existiert vor UUIDv7 und löst dasselbe “zeitgeordnet, verteilt”-Problem, wählt aber eine andere Textkodierung.
Eine ULID ist ebenfalls 128 Bit: 48 Bit Millisekunden-Zeitstempel, 80 Bit Zufälligkeit. Der Unterschied ist, wie man sie aufschreibt. Statt Hex mit Bindestrichen verwendet ULID Crockfords Base32, was Ihnen eine 26-stellige Zeichenfolge ohne Bindestriche gibt:
01ARZ3NDEKTSV4RRFFQ69G5FAV
Crockford Base32 wurde entwickelt, um von Menschen gelesen und ohne Fehler eingegeben zu werden. Es schließt die Buchstaben I, L, O und U aus, sodass man ein Eins und ein I oder eine Null und ein O nicht verwechseln kann, und es ist Groß-/Kleinschreibungsunempfindlich. Die Kodierung ist auch lexikografisch sortierbar, was bedeutet, dass eine einfache Zeichensortierung Ihnen die Zeitreihenfolge kostenlos gibt, was praktisch ist, wenn Ihre Speicherschicht die ID als Text behandelt.
ULIDs Attraktivität gegenüber UUIDv7 war hauptsächlich die 26-Zeichen-Kompakttextform gegenüber UUIDs 36 Zeichen. Jetzt, da v7 ein echten Standard ist, ist die ehrliche Einschätzung, dass ULID und UUIDv7 nahe genug beieinander liegen, dass der entscheidende Faktor die Ökosystem-Unterstützung ist, nicht technisches Verdienst. Wenn Ihre Datenbank einen nativen uuid-Typ hat (Postgres hat einen, und er speichert 16 Bytes), gewinnt UUIDv7, weil ULID keinen nativen Spaltentyp hat und Sie es als char(26)-Text speichern müssen oder Ihre eigene Binärpakierung durchführen. Wenn Sie in einem Schlüssel-Wert-Speicher oder einem System sind, in dem sowieso alles eine Zeichenfolge ist, ist ULIDs kürzerer, saubererer Text wirklich netter.
NanoID
NanoID ist ein anderes Tier. Es ist nicht zeitgeordnet und es sind keine festen 128 Bit. Es ist ein kleiner, konfigurierbarer Zufallsstring-Generator, und sein ganzes Versprechen ist, gut in URLs zu sein.
Der Standard ist 21 Zeichen aus einem 64-stelligen URL-sicheren Alphabet (A-Za-z0-9_-). Das gibt ungefähr 126 Bit Zufälligkeit, was in der gleichen Größenordnung wie UUIDv4s 122 zufälligen Bits liegt, aber in 21 Zeichen statt 36. Sowohl die Länge als auch das Alphabet sind einstellbar.
V1StGXR8_Z5jdHi6B-myT
Weil das Alphabet standardmäßig URL-sicher ist, können Sie eine NanoID direkt in einen Pfad oder Abfrage-String einfügen, ohne prozentkodiert zu werden. Keine Bindestriche, um die man sich kümmern muss, keine Groß-/Kleinschreibungsüberraschungen, und Sie können es verkleinern, wenn Sie keine 126 Bit benötigen. Ein Kurz-Link-Dienst zum Beispiel könnte eine 10-stellige NanoID verwenden. Wenn Sie NanoID-ähnliche Zufallsstrings zum Experimentieren möchten, lässt der Zufallsstring-Generator Länge und Alphabet direkt einstellen, und für kryptografische Sitzungstoken speziell ist der Sichere Token-Generator das richtige Werkzeug.
NanoID ist das, nach dem ich greife, wenn die ID in einer URL lebt, die Menschen sehen oder teilen, und ich sie nicht nach Zeit sortieren muss.
KSUID, kurz
KSUID (K-Sortable Unique Identifier, von Segment) verdient eine Erwähnung, weil Sie es in älteren Systemen sehen werden. Es ist 160 Bit: ein 32-Bit-Sekunden-Zeitstempel plus 128 Bit Zufälligkeit, kodiert als 27-stellige Base62-Zeichenfolge. Es ist sortierbar wie ULID und UUIDv7, aber es ist größer und hat nur Sekundenauflösung beim Zeitstempel. Im Jahr 2026 gibt es wenig Grund, es gegenüber UUIDv7 für neue Arbeit zu wählen, aber es ist ein völlig vernünftiges Schema, wenn Sie es erben.
Die Vergleichsmatrix
| Schema | Bit | Textlange | Zeitgeordnet | URL-sicher out of the box | Nativer DB-Typ | Leckt Zeitstempel |
|---|---|---|---|---|---|---|
| UUIDv4 | 128 | 36 Zeichen | Nein | Nein (hat Bindestriche) | Ja (uuid) | Nein |
| UUIDv7 | 128 | 36 Zeichen | Ja | Nein (hat Bindestriche) | Ja (uuid) | Ja (ms) |
| ULID | 128 | 26 Zeichen | Ja | Ja | Nein (Text speichern) | Ja (ms) |
| NanoID | konfigurierbar (~126 Standard) | 21 Zeichen Standard | Nein | Ja | Nein (Text speichern) | Nein |
| KSUID | 160 | 27 Zeichen | Ja | Ja | Nein (Text speichern) | Ja (Sek.) |
Einige Dinge sind es wert, aus dieser Tabelle herauszuziehen. Nur UUIDv4 und NanoID verbergen ihre Erstellungszeit. Nur UUID hat einen nativen 16-Byte-Datenbankspaltenttyp, den man tatsächlich verwenden sollte. Alles Zeitgeordnete (v7, ULID, KSUID) gibt Ihnen den Index-Lokalitäts-Gewinn bei Inserts; UUIDv4 ist der einzige, der das nicht tut.
Kollisionswahrscheinlichkeit, mit echten Zahlen
Menschen machen sich viel mehr Sorgen um Kollisionen, als die Mathematik rechtfertigt. Das relevante Werkzeug ist die Geburtstags-Schranke: Die Chance jeder Kollision in einem Satz steigt viel schneller als die Intuition sagt, weil jedes Paar von Elementen eine Chance zur Kollision ist. Die grobe Regel ist, dass Sie eine 50%-Chance auf eine Kollision erwarten, sobald Sie etwa die Quadratwurzel des Gesamtraums generiert haben.
Für UUIDv4 ist der Raum 2^122 (die zufälligen Bits). Die Quadratwurzel ist 2^61, etwa 2,3 Milliarden Milliarden. Um eine Zahl auf das Ende mit kleiner Wahrscheinlichkeit zu setzen: Sie müssten ungefähr 1 Milliarde UUIDs pro Sekunde für etwa 85 Jahre generieren, um eine 50%-Chance auf eine einzelne Kollision zu treffen. Für praktische Volumina rundet die Wahrscheinlichkeit auf null. Sie müssen nicht auf v4-Kollisionen prüfen.
NanoID mit seinem 21-Zeichen-Standard ist 2^126, noch größer. Die eigene Tabelle des Projekts drückt es so aus: Bei 1000 IDs pro Sekunde bräuchten Sie etwa 49.000 Jahre, um eine 1%-Chance auf eine Kollision zu erreichen. Der interessante Fall ist, wenn Sie es verkleinern. Verkleinern Sie eine NanoID auf 8 Zeichen mit dem 64-stelligen Alphabet, und Sie haben 2^48 Raum, etwa 281 Billionen. Durch die Geburtstags-Schranke liegt der 50%-Punkt nahe bei 2^24, ungefähr 16,7 Millionen IDs. Ein belebter Kurz-Link-Dienst kann das treffen, also braucht eine kurze NanoID entweder eine Eindeutigkeitsbeschränkung mit Wiederholung bei seltenen Konflikten oder mehr Zeichen. Die Lektion: Das Kollisionsrisiko ist vollständig eine Funktion davon, wie viele Bits Sie behalten, und das Verkürzen einer ID ist genau der Akt des Bits-Wegwerfens.
Entscheidungsleitfaden
Hier ist, wie ich tatsächlich entscheide.
- Primärschlüssel auf einer echten Datenbanktabelle? Verwenden Sie UUIDv7. Nativer
uuid-Spalte, 16 Bytes, zeitgeordnet, sodass Inserts günstig bleiben. Das ist der neue Standard und ersetzt v4 ohne Schema-Änderung. - Öffentlicher Bezeichner in einer URL, die Menschen sehen oder eintippen? Verwenden Sie NanoID. URL-sicher, kompakt und entscheidend leckt es nicht, wann die Zeile erstellt wurde. Wählen Sie eine Länge, die Ihnen die benötigten Bits gibt (21 ist reichlich; gehen Sie nicht unter ~12, ohne über die Geburtstags-Schranke nachzudenken).
- Die ID soll ihre Erstellungszeit selbst intern verbergen? Verwenden Sie UUIDv4 oder eine zufällige NanoID. v7 und ULID legen beide einen Millisekunden-Zeitstempel für jeden offen, der den Wert hält.
- Sie befinden sich in einem nur-String-Speicher (einige KV-Stores, Logs, Event-Streams) und wollen zeitsortierbaren Schlüssel? ULID ist dort schöner zu lesen und einzutippen als UUIDv7, da Sie sowieso Text speichern.
- Sie wollen die Stripe-Erfahrung (
cus_xxx,pi_xxx)? Präparieren Sie eine NanoID mit einem kurzen Typen-Tag:order_V1StGXR8Z5jdHi6BmyT. Das Präfix macht IDs in Logs selbst-beschreibend und hindert Sie daran, eine Kunden-ID dort zu übergeben, wo eine Bestell-ID hingehört. Das ist eine Konvention, die auf NanoID aufgebaut ist, kein separates Schema.
Wenn Sie eine Sache mitnehmen: Hören Sie auf, nach UUIDv4 als Reflex-Primärschlüssel zu greifen. Es war die richtige Antwort im Jahr 2015. UUIDv7 ist die richtige Antwort jetzt, und die Migration ist ein Generator-Austausch, keine Datenmigration.
Zusammenfassung
Auto-Inkrement-Integer sind kompakt und schnell, lecken aber Ihre Datensatzzahlen und können nicht offline generiert werden. UUIDs sind ein 128-Bit-Standard, dessen Versionsnibble Ihnen sagt, wie sie gemacht wurden. UUIDv4 ist reine Zufälligkeit und ruiniert still die B-Baum-Insert-Performance bei großen Tabellen, weil jeder Schlüssel an einem zufälligen Ort landet. UUIDv7 behebt das, indem er mit einem Millisekunden-Zeitstempel beginnt, bleibt eine Drop-in-uuid-Spalte und ist der vernünftige 2026-Standard für Primärschlüssel. ULID löst die gleiche Zeitordnung mit einer 26-stelligen Crockford-Base32-Zeichenfolge und glänzt in nur-String-Speichern. NanoID ist die URL-Wahl: kurz, konfigurierbar, URL-sicher und leckt keinen Zeitstempel, mit Kollisionswahrscheinlichkeiten, die vernachlässigbar bleiben, solange Sie genug Bits behalten. KSUID ist in Ordnung, wenn Sie es erben, aber selten wert, frisch zu wählen. Passen Sie das Schema daran an, wo die ID lebt, achten Sie auf den Zeitstempel-Leak bei den sortierbaren, und respektieren Sie die Geburtstags-Schranke, sobald Sie anfangen, die Länge zu kürzen.
In diesem Artikel erwähnte Tools
- UUID Generator - Generate UUID v4 identifiers, single or in bulk.
- UUID Version Detector - Identify the version (v1, v3, v4, v5, v6, v7, v8) and variant of any UUID and extract embedded timestamps.
- Random String Generator - Generate random strings with configurable length and character set.
- Secure Token Generator - Generate cryptographically secure random tokens in base64url, hex, alphanumeric or UUID v4 format.