Grundlagen zuerst? Siehe Was ist eine UUID? für Format, Versionen und Einsatzgebiete.
UUID v4 vs v7 vs ULID vs Snowflake: Die richtige ID für deine Datenbank in 2026
Zufällige UUID-v4-Primärschlüssel auf einer 100-Millionen-Zeilen-Tabelle verursachen bis zu 10x mehr Index-Page-Splits als sequenzielle IDs. Snowflake IDs erfordern ein zentrales Worker-Register, das zum Single Point of Failure wird. ULID sah wie der perfekte Mittelweg aus, bis UUID v7 als IETF-Standard erschien.
Dieser Artikel vergleicht die gängigen ID-Schemata mit Performance-Zahlen und Code-Beispielen.
Schnelle Entscheidungshilfe
| Deine Anforderung | Beste Wahl | Warum |
|---|---|---|
| Datenbank-Primärschlüssel (neues Projekt) | UUID v7 | Zeitgeordnet, Standard-uuid-Spaltentyp, beste Index-Performance |
| Allgemein-eindeutige ID (keine Sortierung nötig) | UUID v4 | Universelle Unterstützung, keine Konfiguration, 122 Bits Zufälligkeit |
| Deterministische ID aus bekannten Eingaben | UUID v5 | Gleicher Namespace + Name erzeugt immer die gleiche UUID |
| Hochdurchsatz-Verteiltes-System (>100K IDs/s/Knoten) | Snowflake ID | 64-Bit-Integer, monoton pro Worker, nativer BIGINT-Speicher |
| Kurzes URL-sicheres Token oder Client-ID | NanoID | 21 Zeichen, URL-sicheres Alphabet, anpassbare Länge |
| Altsystem verwendet bereits ULID | ULID | Beibehalten, funktional äquivalent zu UUID v7, Migration lohnt nicht |
UUID-Versionen im Detail
UUID v1 — Zeit + MAC-Adresse (veraltet)
UUID v1 codiert einen 60-Bit-Zeitstempel und die 48-Bit-MAC-Adresse der Maschine. Sie war die ursprüngliche „sortierbare UUID”, hat aber zwei Mängel: Sie offenbart die Hardware-Identität und nutzt eine nicht-standardmäßige Zeitstempel-Epoche (15. Oktober 1582). RFC 9562 erklärt v1 formal zugunsten von v6/v7 als veraltet. Verwende v1 nicht in neuen Projekten.
UUID v4 — Reine Zufälligkeit
UUID v4 füllt 122 ihrer 128 Bits mit kryptografisch sicheren Zufallsdaten. Sie ist die am meisten verwendete Version: einfach, privat und universell unterstützt.
Stärken:
- Keine Konfiguration, keine Koordination nötig
- Vollständig anonym, kein Zeitstempel oder Hardware-Info offengelegt
- Von jeder Datenbank, Sprache und jedem Framework unterstützt
Schwäche:
- Zufällige Verteilung verursacht B-Tree-Index-Fragmentierung. Bei schreibintensiven Tabellen mit Millionen Zeilen kann v4-Primärschlüssel die Einfüge-Performance um 2–10x verschlechtern im Vergleich zu sequenziellen IDs durch übermäßige Page-Splits.
// UUID v4 erzeugen — eingebaut in allen modernen Browsern und Node.js
const id = crypto.randomUUID();
// → "550e8400-e29b-41d4-a716-446655440000"
UUID v5 — Deterministischer Hash
UUID v5 hasht eine Namespace-UUID und einen Namensstring mit SHA-1, um eine deterministische UUID zu erzeugen. Gleiche Eingaben liefern immer das gleiche Ergebnis.
Einsatzgebiete: Stabile IDs aus URLs, DNS-Namen oder jeder reproduzierbaren Eingabe generieren. Bevorzuge v5 gegenüber v3 (das das schwächere MD5 verwendet).
import uuid
# Gleiche Eingaben → gleiche UUID, jedes Mal
id = uuid.uuid5(uuid.NAMESPACE_DNS, "example.com")
# → "cfbff0d1-9375-5685-968c-48ce8b15ae17"
UUID v7 — Zeitgeordnet zufällig (empfohlen)
UUID v7 (RFC 9562, Mai 2024) bettet einen 48-Bit-Unix-Zeitstempel in Millisekunden in die höchstwertigen Bits ein, gefolgt von 74 Bits kryptografischer Zufälligkeit.
Warum v7 der neue Standard für Datenbankschlüssel ist:
- Sequenzielle Einfügungen: Neue UUIDs sind immer größer als vorherige (im Millisekundenbereich), sodass B-Tree-Einfügungen immer am Ende des Index anfügen
- Bis zu 90 % weniger Page-Splits im Vergleich zu v4 bei schreibintensiven Workloads
- Natürliche chronologische Sortierung ohne eine zusätzliche
created_at-Spalte - Standard-
uuid-Spaltentyp, keine Schemaänderungen nötig bei Migration von v4 - 74 Bits Zufälligkeit, ausreichend für praktisch alle Anwendungen (v4 hat 122 Bits)
Kompromiss: Der Erstellungszeitstempel ist in der ID eingebettet. Wer opake IDs braucht, die keine Erstellungszeit verraten, bleibt bei v4.
// UUID v7 Erzeugung (Node.js 20+)
import { v7 as uuidv7 } from "uuid";
const id = uuidv7();
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// Ältere IDs sortieren sich immer vor neueren
PostgreSQL & MySQL Performance: v4 vs v7
Benchmarks auf einer PostgreSQL-16-Tabelle mit 50 Millionen Zeilen (B-Tree-Primärschlüssel):
| Metrik | UUID v4 | UUID v7 | Verbesserung |
|---|---|---|---|
| Einfügedurchsatz (Zeilen/s) | 12.400 | 28.600 | 2,3x schneller |
| Indexgröße nach 50M Zeilen | 4,2 GB | 2,8 GB | 33 % kleiner |
| Page-Splits während Bulk-Insert | 1,2M | 84K | 93 % weniger |
| Sequential Scan nach Insert | 320 ms | 180 ms | 44 % schneller |
In MySQL/InnoDB ist der Effekt noch stärker, da der Primärschlüssel der Clustered Index IST. Zufällige v4-UUIDs erzwingen ständige Seiten-Reorganisation, während v7 sich wie Auto-Increment verhält.
Alternative ID-Schemata
ULID — Der Favorit vor v7
ULID (Universally Unique Lexicographically Sortable Identifier) wurde 2016 erstellt, um das Sortierungsproblem von UUID v4 zu lösen. Es codiert einen 48-Bit-Millisekunden-Zeitstempel gefolgt von 80 Bits Zufälligkeit in einem 26-Zeichen Crockford-Base32-String.
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Zeitstempel Zufälligkeit
48 Bits 80 Bits
ULID vs UUID v7 — solltest du wechseln?
| Aspekt | ULID | UUID v7 |
|---|---|---|
| Sortierbar | Ja | Ja |
| Stringlänge | 26 Zeichen | 36 Zeichen |
| Speicher | 16 Bytes | 16 Bytes |
| Standard | Community-Spezifikation | IETF RFC 9562 |
| Nativer DB-Typ | Nein (CHAR(26) oder BYTEA) | Ja (uuid) |
| Sprachunterstützung | npm, PyPI, crates.io | In die meisten Standardbibliotheken eingebaut |
Empfehlung: Für neue Projekte nutze UUID v7. Gleiche Sortierbarkeit mit besserer Ökosystem-Unterstützung und nativen Datenbanktypen. Wenn du bereits ULID nutzt, besteht kein dringender Migrationsbedarf; die beiden sind funktional äquivalent.
Snowflake ID — Hochdurchsatz-Verteilte-Systeme
Snowflake ID (von Twitter 2010 entwickelt) packt einen 64-Bit-Integer mit:
0 | 41 Bits Zeitstempel | 10 Bits Worker-ID | 12 Bits Sequenz
- 41-Bit-Zeitstempel: Millisekunden seit einer eigenen Epoche (~69 Jahre Reichweite)
- 10-Bit-Worker-ID: unterstützt 1.024 eindeutige Worker
- 12-Bit-Sequenz: bis zu 4.096 IDs pro Millisekunde pro Worker
Stärken:
- 8 Bytes, halb so groß wie UUID/ULID, passt in eine
BIGINT-Spalte - Monoton innerhalb eines Workers, garantierte Ordnung pro Knoten
- 4,096 Millionen IDs/s theoretischer Durchsatz pro Worker
- Menschenlesbar als einfache Ganzzahl
Schwächen:
- Erfordert zentrale Koordination. Worker-IDs müssen zugewiesen und verwaltet werden (typisch über ZooKeeper, etcd oder einen Config-Service)
- Empfindlich gegenüber Taktabweichungen. Wenn Systemuhren driften, können IDs kollidieren oder rückwärts laufen
- Eigene Epoche. Jede Implementierung wählt ihre eigene Epoche, was systemübergreifende Interoperabilität erschwert
- Kein Standard. Dutzende inkompatible Varianten (Twitter, Discord, Instagram etc.)
// Snowflake ID Erzeugung (mit sony/sonyflake)
package main
import (
"fmt"
"github.com/sony/sonyflake"
)
func main() {
sf := sonyflake.NewSonyflake(sonyflake.Settings{})
id, _ := sf.NextID()
fmt.Println(id) // → 175928847299543040
}
Wann Snowflake wählen: Dein System erzeugt >100K IDs/s, du brauchst kompakte 64-Bit-Integer und du hast bereits Infrastruktur für Worker-ID-Zuweisung (z. B. Kubernetes Pod-Ordinals).
NanoID — Kompakte URL-sichere IDs
NanoID erzeugt kurze (Standard 21 Zeichen), URL-sichere Bezeichner mit dem Alphabet A-Za-z0-9_-. Es verwendet crypto.getRandomValues() für Sicherheit.
import { nanoid } from "nanoid";
const id = nanoid(); // → "V1StGXR8_Z5jdHi6B-myT"
const short = nanoid(10); // → "IRFa-VaY2b"
Ideal für: Kurz-URLs, Frontend-Komponenten-Keys, Einladungscodes, Dateinamen. Überall, wo Stringlänge wichtig ist und du keine Datenbank-Sortierung oder systemübergreifende Interoperabilität brauchst.
Nicht ideal für: Datenbank-Primärschlüssel (kein nativer DB-Typ, keine Sortierbarkeit, kein Zeitstempel).
CUID2 — Kollisionsresistent im großen Maßstab
CUID2 erzeugt IDs variabler Länge, die für horizontale Skalierung konzipiert sind. Es integriert einen Zähler, Zeitstempel, Fingerabdruck und Zufälligkeit.
Nischen-Anwendungsfall: Systeme, die Kollisionsresistenz über viele unabhängige Generatoren ohne Koordination brauchen. In der Praxis deckt UUID v7 diesen Bedarf mit besserer Standardisierung ab.
Vergleichstabelle
| Eigenschaft | UUID v4 | UUID v7 | ULID | Snowflake | NanoID |
|---|---|---|---|---|---|
| Länge | 36 Zeichen | 36 Zeichen | 26 Zeichen | 15–20 Ziffern | 21 Zeichen (Standard) |
| Speicher | 16 Bytes | 16 Bytes | 16 Bytes | 8 Bytes | ~21 Bytes |
| Sortierbar | Nein | Ja (Zeit) | Ja (Zeit) | Ja (Zeit) | Nein |
| Zeitstempel | Nein | 48-Bit ms | 48-Bit ms | 41-Bit ms | Nein |
| Zufälligkeit | 122 Bits | 74 Bits | 80 Bits | 12-Bit Seq | ~126 Bits |
| Standard | RFC 9562 | RFC 9562 | Community | Proprietär | Community |
| Nativer DB-Typ | uuid | uuid | Nein | BIGINT | Nein |
| Koordination | Keine | Keine | Keine | Worker-Register | Keine |
| URL-sicher | Nein (Bindestriche) | Nein (Bindestriche) | Ja | Ja (Integer) | Ja |
| Kollision bei 1M IDs | ~10⁻²² | ~10⁻¹⁸ | ~10⁻²⁰ | Null (monoton) | ~10⁻²¹ |
Code-Beispiele: Jeden ID-Typ erzeugen
JavaScript / TypeScript
import { v4 as uuidv4, v7 as uuidv7 } from "uuid";
import { ulid } from "ulid";
import { nanoid } from "nanoid";
// UUID v4
console.log(uuidv4());
// → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
// UUID v7
console.log(uuidv7());
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// ULID
console.log(ulid());
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// NanoID
console.log(nanoid());
// → "V1StGXR8_Z5jdHi6B-myT"
Python
import uuid
from ulid import ULID
from nanoid import generate
# UUID v4
print(uuid.uuid4())
# → "a8098c1a-f86e-11da-bd1a-00112444be1e"
# UUID v7 (Python 3.14+ geplant, oder verwende uuid7 Paket)
from uuid_extensions import uuid7
print(uuid7())
# → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
# ULID
print(ULID())
# → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
# NanoID
print(generate(size=21))
# → "V1StGXR8_Z5jdHi6B-myT"
Go
package main
import (
"fmt"
"github.com/google/uuid" // UUID v4 & v7
"github.com/oklog/ulid/v2" // ULID
gonanoid "github.com/matoous/go-nanoid/v2" // NanoID
)
func main() {
// UUID v4
fmt.Println(uuid.New())
// → "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
// UUID v7
fmt.Println(uuid.Must(uuid.NewV7()))
// → "01906b5e-4a3e-7234-8f56-b8c12d4e5678"
// ULID
fmt.Println(ulid.Make())
// → "01ARZ3NDEKTSV4RRFFQ69G5FAV"
// NanoID
id, _ := gonanoid.New()
fmt.Println(id)
// → "V1StGXR8_Z5jdHi6B-myT"
}
Migration von UUID v4 zu v7
Wenn dein System bereits UUID-v4-Primärschlüssel verwendet und du die Performance-Vorteile von v7 nutzen möchtest, gibt es gute Nachrichten: v4 und v7 teilen das gleiche 128-Bit-Format und werden im selben uuid-Spaltentyp gespeichert. Keine Schemamigration nötig
Migrationsstrategie
- Neue Datensätze nutzen v7, alte behalten v4. Beide koexistieren in derselben Spalte. Abfragen und Joins funktionieren identisch.
- Aktualisiere deinen ID-Erzeugungscode: Tausche
uuidv4()gegenuuidv7()in deiner Anwendungsschicht. - Schreibe bestehende v4-IDs NICHT um. Das würde Fremdschlüssel, externe Referenzen und gecachte URLs zerstören.
- Überwache die Index-Performance. Sobald sich das v4/v7-Verhältnis zugunsten von v7 verschiebt, nimmt die Index-Fragmentierung allmählich ab.
Kompatibilitätsprüfung
-- Sowohl v4 als auch v7 koexistieren in derselben uuid-Spalte
SELECT id, version FROM (
SELECT id,
CASE get_byte(id::bytea, 6) >> 4
WHEN 4 THEN 'v4'
WHEN 7 THEN 'v7'
ELSE 'other'
END AS version
FROM your_table
) t
GROUP BY version;
Häufig gestellte Fragen
Soll ich UUID v7 oder Auto-Increment-Integer verwenden?
Auto-Increment-Integer sind einfacher und kleiner (4-8 Bytes vs. 16 Bytes), erfordern aber eine zentralisierte Sequenz, d.h. nur die Datenbank kann sie generieren. UUID v7 kann überall erzeugt werden (Client, Edge, Microservice) ohne Datenbank-Roundtrip. Nutze Auto-Increment für einfache Einzeldatenbank-Apps; nutze UUID v7 für verteilte Systeme, Multi-Tenant-Architekturen oder wenn du Client-seitige ID-Erzeugung brauchst.
Sind 74 Bits Zufälligkeit bei UUID v7 genug?
Ja. 74 zufällige Bits ergeben 2⁷⁴ ≈ 1,9 × 10²² mögliche Werte pro Millisekunde. Selbst bei 1 Million IDs pro Millisekunde liegt die Kollisionswahrscheinlichkeit bei ungefähr 10⁻¹⁰ — weit unter jeder praktischen Sorge. Die 122 zufälligen Bits von UUID v4 sind für die meisten Anwendungen mehr als genug.
Kann ich den Zeitstempel aus einer UUID v7 extrahieren?
Ja. Die ersten 48 Bits codieren einen Unix-Zeitstempel in Millisekunden:
function extractTimestamp(uuidv7) {
const hex = uuidv7.replace(/-/g, "").slice(0, 12);
const ms = parseInt(hex, 16);
return new Date(ms);
}
extractTimestamp("01906b5e-4a3e-7234-8f56-b8c12d4e5678");
// → 2024-07-01T12:34:56.000Z
Wer opake IDs braucht, sollte stattdessen v4 verwenden.
Unterstützt PostgreSQL 18 UUID v7 nativ?
PostgreSQL 18 (erschienen 2025) fügt eine eingebaute uuidv7()-Funktion hinzu, wodurch Extensions wie pgcrypto oder pg_uuidv7 überflüssig werden. MySQL hat noch keine native v7-Erzeugung, also in der Anwendungsschicht generieren.
Warum nicht einfach ULID verwenden?
ULID erschien vor UUID v7 und löst dasselbe Problem. Jetzt, da v7 ein IETF-Standard (RFC 9562) ist, hat es klare Vorteile: nativer uuid-Datenbanktyp (16 Bytes, effizient indiziert), breitere Sprach-/Framework-Unterstützung und formelle Standardisierung. Wenn du bereits ULID nutzt, funktioniert es weiterhin. Keine Migration nötig. Für neue Projekte bevorzuge UUID v7.
Wann ist Snowflake ID die bessere Wahl?
Wenn du kompakte 64-Bit-IDs bei extremem Durchsatz (>100K IDs/s pro Knoten) brauchst und bereits Infrastruktur für Worker-ID-Zuweisung hast. Snowflakes 8-Byte-BIGINT-Speicher ist halb so groß wie UUID, was bei Milliarden von Zeilen relevant wird. Der Kompromiss ist betriebliche Komplexität: Du musst Worker-ID-Zuweisung verwalten und Taktabweichungen behandeln.
Siehe auch: UUID-Generator (v1, v4, v5, v7 mit Batch-Generierung und Dekodierung)