UUID vs. GUID: Was sich tatsächlich unterscheidet
GUID ist Microsofts Name für eine UUID. Die Unterschiede, die Ihnen in der Praxis begegnen, sind die Textdarstellung, die Bytereihenfolge beim Speichern als 16 Bytes und die Sortierung in SQL Server.
Die kurze Antwort vorweg: GUID und UUID bezeichnen dieselbe Art von Wert, einen 128-Bit-Bezeichner, der meist als 32 Hex-Ziffern in fünf Gruppen geschrieben wird. Guid.NewGuid() in .NET und crypto.randomUUID() in JavaScript erzeugen beide zufällige UUIDs der Version 4. Trotzdem kommt es immer wieder zu Problemen zwischen Systemen, und die haben drei Ursachen: wie der Wert als Text geschrieben wird, in welcher Reihenfolge seine 16 Bytes stehen und wie SQL Server ihn vergleicht.
Zwei Namen für einen Standard
UUIDs stammen aus dem Network Computing System von Apollo und wurden in das Distributed Computing Environment (DCE) der OSF übernommen. Microsoft übernahm das DCE-Design für COM und nannte die Werte Globally Unique Identifiers. Die IETF veröffentlichte das Format später im Juli 2005 als RFC 4122, der im Mai 2024 durch RFC 9562 ersetzt wurde – die Fassung, die UUID v6, v7 und v8 ergänzt hat. Beide RFCs sagen ausdrücklich, dass UUIDs auch als GUIDs bekannt sind.
Ein Feld hält diese Namensgeschichte noch fest. Die Variantenbits am Anfang der vierten Gruppe unterscheiden RFC-UUIDs, bei denen diese Gruppe mit 8, 9, a oder b beginnt, von einem Bereich, der für die Abwärtskompatibilität mit Microsoft reserviert ist und mit c oder d beginnt. GUIDs, die aktuelle Windows- und .NET-Versionen erzeugen, verwenden die RFC-Variante, in der Praxis sind die beiden also nicht zu unterscheiden.
Textformate
Guid.ToString() in .NET akzeptiert einen Formatbezeichner. Das sind die Ausgaben für denselben Wert in .NET 8:
| Bezeichner | Ausgabe |
|---|---|
N | f47ac10b58cc4372a5670e02b2c3d479 |
D (Standard) | f47ac10b-58cc-4372-a567-0e02b2c3d479 |
B | {f47ac10b-58cc-4372-a567-0e02b2c3d479} |
P | (f47ac10b-58cc-4372-a567-0e02b2c3d479) |
X | {0xf47ac10b,0x58cc,0x4372,{0xa5,0x67,0x0e,0x02,0xb2,0xc3,0xd4,0x79}} |
Alle Ausgaben sind kleingeschrieben. Die Großschreibung mit geschweiften Klammern, {F47AC10B-58CC-4372-A567-0E02B2C3D479}, stammt aus der Windows-Registry, aus COM-Klassen-IDs und Visual-Studio-Projektdateien, und auch SQL Server zeigt uniqueidentifier-Werte in Großbuchstaben an. RFC 4122 verlangt Kleinbuchstaben bei der Ausgabe und Unabhängigkeit von der Schreibung bei der Eingabe, und Guid.Parse akzeptiert jede Form aus der Tabelle.
Die praktische Regel lautet: Vergleichen Sie GUIDs nie als Strings. "{F47AC10B-...}" und "f47ac10b-..." sind verschiedene Strings, aber derselbe Wert. Parsen Sie beide Seiten, oder normalisieren Sie vor dem Speichern oder Vergleichen auf Kleinbuchstaben ohne Klammern.
Bytereihenfolge: der Unterschied, der wehtut
Als 16 Bytes gespeichert, ist eine RFC-UUID Big-Endian: Die Bytes stehen in derselben Reihenfolge wie die Hex-Ziffern. Microsofts GUID-Struktur ist anders. Sie ist als eine 32-Bit-Ganzzahl, zwei 16-Bit-Ganzzahlen und 8 einzelne Bytes deklariert, und auf Little-Endian-Maschinen werden die drei Ganzzahlen mit umgekehrter Bytereihenfolge gespeichert. Guid.ToByteArray() liefert genau dieses Layout, und new Guid(byte[]) erwartet es:
Text: f47ac10b-58cc-4372-a567-0e02b2c3d479
RFC bytes: f4 7a c1 0b 58 cc 43 72 a5 67 0e 02 b2 c3 d4 79
ToByteArray(): 0b c1 7a f4 cc 58 72 43 a5 67 0e 02 b2 c3 d4 79
Die letzten 8 Bytes sind identisch, die ersten drei Gruppen sind umgedreht. SQL Server verwendet dasselbe gemischte Layout: Wandeln Sie einen uniqueidentifier in binary(16) um, erhalten Sie die Bytes von ToByteArray().
Der Fehler entsteht, wenn eine Seite die Bytes im einen Layout schreibt und die andere sie im anderen liest. Ein .NET-Dienst schreibt ToByteArray() in eine BINARY(16)-Spalte oder eine binäre Nachricht; ein Dienst in Java, Go oder Python liest die Bytes als RFC-UUID und erhält 0bc17af4-cc58-7243-a567-0e02b2c3d479. Der Wert ist immer noch eindeutig, es schlägt also nichts fehl, aber Abfragen zwischen den beiden Systemen finden nie einen Treffer. Auch die Versionsziffer ist gewandert: In diesem Beispiel gibt sich der verdrehte Wert als Version 7 aus.
Legen Sie sich auf ein Layout fest und konvertieren Sie an der Systemgrenze ausdrücklich:
- .NET 8 und neuer:
guid.ToByteArray(bigEndian: true)undnew Guid(bytes, bigEndian: true)schreiben und lesen die RFC-Reihenfolge. - Python:
uuid.UUID(bytes_le=data)liest das Microsoft-Layout, undu.bytes_leschreibt es. - Überall: Wenn Sie statt roher Bytes Text oder einen nativen UUID-Typ speichern, stellt sich die Frage gar nicht erst.
Wie SQL Server uniqueidentifier sortiert
SQL Server sortiert GUIDs nicht so, wie sich ihr Text liest. Er vergleicht zuerst die letzte Gruppe aus sechs Bytes, dann die vierte Gruppe, danach die dritte, zweite und erste Gruppe, und innerhalb dieser ersten drei Gruppen zählt das Byte ganz rechts am meisten. Die folgende Liste steht in aufsteigender uniqueidentifier-Reihenfolge, erzeugt mit dem .NET-Typ SqlGuid, der denselben Vergleich implementiert:
01000000-0000-0000-0000-000000000000
ff000000-0000-0000-0000-000000000000
00000001-0000-0000-0000-000000000000
00000000-0100-0000-0000-000000000000
00000000-0001-0000-0000-000000000000
00000000-0000-0100-0000-000000000000
00000000-0000-0001-0000-000000000000
00000000-0000-0000-0001-000000000000
00000000-0000-0000-0100-000000000000
00000000-0000-0000-0000-000000000002
00000000-0000-0000-0000-010000000000
Sortiert man dieselben Werte als System.Guid in .NET oder als Strings in einer beliebigen Sprache, ergibt sich eine andere Reihenfolge: 00000000-0000-0000-0000-000000000002 kommt zuerst und ff000000-0000-0000-0000-000000000000 zuletzt. Daraus folgen zwei Dinge:
- Keyset-Paginierung kann Zeilen überspringen oder doppelt liefern, wenn die Anwendung GUIDs selbst sortiert und SQL Server dann nach Zeilen mit
WHERE id > @lastfragt. Überlassen Sie beides der Datenbank. - UUID v7 hilft in SQL Server nicht. Ein v7-Wert beginnt zwar mit einem Zeitstempel, SQL Server vergleicht aber zuerst das zufällige Ende. v7-Schlüssel, auch die aus
Guid.CreateVersion7()in .NET 9, landen in einemuniqueidentifier-Index deshalb genauso an zufälligen Positionen wie v4-Schlüssel.
NEWID vs. NEWSEQUENTIALID
NEWID() | NEWSEQUENTIALID() | |
|---|---|---|
| Wie der Wert entsteht | Zufällig, Version 4 | Windows-Funktion UuidCreateSequential, eine zeitbasierte UUID mit umsortierten Bytes |
| Wo Sie ihn verwenden können | Überall in T-SQL | Nur in einer DEFAULT-Einschränkung für eine uniqueidentifier-Spalte |
| Reihenfolge | Zufällig | Aufsteigend in der Sortierung von SQL Server auf diesem Rechner; kann nach einem Windows-Neustart niedriger neu beginnen |
| Vorhersagbar | Nein | Ja: Der nächste Wert lässt sich erraten |
| Verhalten im Index | Inserts landen irgendwo und verursachen Seitenteilungen | Inserts landen am Ende des Index |
NEWSEQUENTIALID() gibt es, weil zufällige Schlüssel gruppierte Indizes fragmentieren. Die umsortierten Bytes sorgen dafür, dass die Werte unter dem ungewöhnlichen Vergleich von SQL Server ansteigen. Genau deshalb ergibt eine Sortierung als Text auch nicht die Reihenfolge, in der sie erzeugt wurden. Weil sich die Werte erraten lassen, rät die Microsoft-Dokumentation davon ab, wo Datenschutz eine Rolle spielt; halten Sie solche IDs aus URLs heraus. Werden die Schlüssel stattdessen in der Anwendung erzeugt, müssen für eine Sortierung wie in SQL Server die zeitbasierten Bytes in die letzte Gruppe – also genau umgekehrt zu dem, was UUID v7 macht.
Beides erzeugen
Da die Werte identisch sind, ist jeder UUID-v4-Generator auch ein GUID-Generator. Der GUID-Generator erzeugt zufällige Werte der Version 4 im Registry-Stil, also in Großbuchstaben mit geschweiften Klammern, und der UUID-v4-Generator erzeugt dieselbe Art von Wert in der kleingeschriebenen RFC-Form. Bei beiden lassen sich Klammern, Groß- und Kleinschreibung und Bindestriche umschalten, womit die Formate N, D und B von oben abgedeckt sind.