Eine UUID (Universally Unique Identifier) ist ein 128-Bit-Wert, der ohne zentrale Koordination weltweit eindeutig sein soll. Praktisch in verteilten Systemen, als „undurchschaubare” Public-IDs in URLs und überall dort, wo bigint-IDs Probleme bereiten würden. PostgreSQL hat einen eigenen uuid-Typ und seit Version 13 die eingebaute gen_random_uuid()-Funktion — keine Extension mehr nötig.

Wie eine UUID aussieht

SQL
myapp=> SELECT gen_random_uuid();
          gen_random_uuid
--------------------------------------
 3f9a8b1c-4e2d-4a1f-9e7c-1a2b3c4d5e6f

128 Bit = 32 Hex-Zeichen, formatiert in 5 Gruppen mit Bindestrichen (8-4-4-4-12). Postgres speichert das intern binär in 16 Bytes, nicht als 36-Zeichen-String — dadurch sind UUID-Vergleiche und -Indexe so schnell wie bigint (mit dem doppelten Speicher).

Tabellen-Definition mit UUID

SQL UUID als Primary Key
CREATE TABLE orders (
    id          uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    customer_id uuid NOT NULL,
    total       numeric(10,2) NOT NULL,
    created_at  timestamptz NOT NULL DEFAULT now()
);

INSERT INTO orders (customer_id, total) VALUES
    ('11111111-1111-1111-1111-111111111111', 99.95),
    ('11111111-1111-1111-1111-111111111111', 49.95);

SELECT * FROM orders;
--                  id                  |             customer_id              | total | created_at
-- --------------------------------------+--------------------------------------+-------+--------------
--  3f9a8b1c-4e2d-4a1f-9e7c-1a2b3c4d5e6f | 11111111-1111-1111-1111-111111111111 | 99.95 | 2026-05-07 …
--  a1b2c3d4-e5f6-7890-abcd-ef0123456789 | 11111111-1111-1111-1111-111111111111 | 49.95 | 2026-05-07 …

gen_random_uuid() als Default sorgt dafür, dass die id automatisch generiert wird, sobald jemand ohne explizite ID einfügt — analog zu GENERATED AS IDENTITY bei bigint.

UUID-Versionen

Die UUID-Spec definiert mehrere „Versionen”, die sich darin unterscheiden, wie der Wert generiert wird:

VersionWie generiertVorteileNachteile
UUIDv4komplett zufälligeinfach, kollisionssicher genugnicht sortierbar, schlechte Index-Lokalität
UUIDv1Zeitstempel + MAC-Adressesortierbar nach Zeitleakt MAC-Adresse, MAC-Spoofing möglich
UUIDv6wie v1, aber Zeitstempel zuerstsortierbarselten verwendet
UUIDv7Unix-Timestamp + Zufallsbitssortierbar nach Zeit, gute Index-Lokalitätneueste Spec (RFC 9562, 2024)

gen_random_uuid() produziert UUIDv4. Für Tabellen mit hohen Insert-Raten wird UUIDv4 zur Performance-Falle: weil die Werte zufällig sind, kann der B-tree-Index nicht inkrementell wachsen — jeder Insert landet an zufälliger Stelle, was zu Page-Splits und schlechter Cache-Lokalität führt.

UUIDv7 — sortierbare UUIDs (PG 18+)

PostgreSQL 18 bringt eine eingebaute uuidv7()-Funktion:

SQL
myapp=> SELECT uuidv7();
            uuidv7
--------------------------------------
 018f1234-5678-7abc-def0-123456789abc
          ^^^^^ Zeitstempel (Millisekunden seit Unix-Epoche)
                        ^   Version 7

Vorteile gegenüber UUIDv4:

  • Sortierbar nach Erstellungszeit (ORDER BY id ist quasi ORDER BY created_at).
  • Bessere Index-Performance — neue Werte landen am Ende des B-trees.
  • Globale Eindeutigkeit wie v4.
SQL UUIDv7 als Default-Pattern (PG 18+)
CREATE TABLE events (
    id         uuid PRIMARY KEY DEFAULT uuidv7(),
    event_type text NOT NULL,
    occurred_at timestamptz NOT NULL DEFAULT now()
);

Auf PG 17 oder älter: per Extension oder mit eigener PL/pgSQL-Funktion — Bibliotheken wie pg_uuidv7 schließen die Lücke.

uuid-ossp — die alte Extension

Vor PostgreSQL 13 brauchte man eine Extension für UUID-Generierung:

SQL Legacy: uuid-ossp
CREATE EXTENSION "uuid-ossp";

SELECT uuid_generate_v4();    -- entspricht heute gen_random_uuid()
SELECT uuid_generate_v1();    -- v1 mit MAC-Adresse
SELECT uuid_generate_v1mc();  -- v1 mit Random-MAC (privater)

Auf neuen Setups (PG 13+) braucht man uuid-ossp nur noch, wenn man andere Versionen als v4 oder v7 will (also v1, v3, v5). Für den Standard-Use-Case gen_random_uuid() / uuidv7() reicht das Built-in.

UUID vs. bigint — Vergleich

Aspektbigint IDENTITYuuid (v4)uuid (v7)
Speicher8 Bytes16 Bytes16 Bytes
GenerierungSequenz im Serverüberall, ohne Serverüberall, ohne Server
Sortierbarja (nach Erstellung)neinja (nach Erstellung)
Index-Performanceoptimalschlecht (Random-Inserts)gut
Verteilbarnein (zentrale Sequenz)jaja
In URLsZähler-leakundurchschaubarundurchschaubar
Lesbar / merkbarjaneinnein
Kollisionssicherja (innerhalb DB)praktisch japraktisch ja

Empfehlungen:

  • Klassische Web-App, eine DB → bigint IDENTITY reicht.
  • Du brauchst Public-IDs in URLs, ohne dass User die Reihenfolge erraten können → eigene uuid-Spalte zusätzlich zur bigint id (interne PK).
  • Verteiltes System mit mehreren Schreibern, die ohne Sync IDs vergeben → uuid (idealerweise v7).
  • Ereignis-Logs mit hohem Insert-Volumen → uuid v7 für Index-Lokalität.

Hybrid-Pattern: bigint intern + uuid extern

Häufig in Web-Apps:

SQL Beste aus zwei Welten
CREATE TABLE orders (
    id          bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    public_id   uuid NOT NULL UNIQUE DEFAULT gen_random_uuid(),
    customer_id bigint NOT NULL REFERENCES customers(id),
    total       numeric(10,2) NOT NULL,
    created_at  timestamptz NOT NULL DEFAULT now()
);

Vorteile:

  • id als interner Primary Key bleibt klein (8 Bytes) und ermöglicht effiziente Foreign Keys.
  • public_id wird in URLs verwendet (/orders/3f9a8b1c-...), erlaubt keine Reihenfolge-Erratung.
  • Foreign Keys laufen weiterhin über id — performant, klein, und verraten in der Tabelle nichts.

Nachteile: zwei Spalten statt einer, also etwas mehr Speicher und ein zusätzlicher UNIQUE-Index.

Besonderheiten

UUIDv4 sind nicht sicher — nur unvorhersehbar

UUIDs sind nicht kryptographisch — sie verraten dem Empfänger nichts, sind aber nicht für Authentifizierung gedacht. Wer einen privaten Token braucht, sollte gen_random_bytes(32) plus Base64-Encoding nehmen, nicht UUID.

UUID-Strings akzeptiert Postgres in mehreren Schreibweisen.

Mit oder ohne Bindestriche, in Klammern, mit urn:uuid:-Präfix — alles wird akzeptiert: '3f9a8b1c4e2d4a1f9e7c1a2b3c4d5e6f', '{3f9a...}', 'urn:uuid:3f9a...'. Postgres normalisiert das beim Einlesen auf die Standard-Schreibweise.

uuid_generate_v1 leakt die MAC-Adresse.

Die alte v1-UUID enthält die MAC-Adresse des erstellenden Hosts und einen Zeitstempel. In Multi-Tenant-Umgebungen kann das die Server-Infrastruktur verraten. v1mc (Random MAC) entschärft das, ist aber nicht mehr deterministisch eindeutig pro Host. Modern: v4 oder v7.

Storage-Overhead vs. Performance-Lokalität.

16 Bytes pro UUID statt 8 Bytes pro bigint klingt klein, summiert sich aber: bei 100 Mio. Zeilen sind das 800 MB Differenz allein für die Primary-Key-Spalte. Plus aufgeblähte Foreign Keys in jeder referenzierenden Tabelle. Auf modernen Disks meist nicht der Engpass — aber Cache-Lokalität schon.

UUIDs in JSON-APIs: als String, nicht als Binary.

jsonb speichert UUIDs als String ({"id": "3f9a..."}), wenn man to_jsonb(uuid_col) nutzt. Das ist gut — JSON kennt keine Binär-Werte. Beim Lesen aus JSON: (payload->>'id')::uuid für expliziten Cast.

UUIDv7 ist neu — Tooling holt nach.

ORM-Frameworks und einige Treiber unterstützen UUIDv7 erst seit kurzem nativ. Wer es einsetzt, sollte prüfen, ob die eigene Stack-Schicht mitspielt. Im Zweifel funktioniert UUIDv7 als opaker Wert — die Sortier-Eigenschaft bleibt erhalten, weil sie aus der UUID selbst kommt, nicht aus dem Treiber.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Datentypen

Zur Übersicht