UUID ou GUID : ce qui diffère vraiment
GUID est le nom que Microsoft donne aux UUID. Les différences rencontrées en pratique tiennent à la mise en forme du texte, à l'ordre des octets quand la valeur est stockée sur 16 octets et à l'ordre de tri de SQL Server.
Si vous voulez simplement la réponse courte : un GUID et un UUID sont le même type de valeur, un identifiant de 128 bits généralement écrit sous forme de 32 chiffres hexadécimaux répartis en cinq groupes. Guid.NewGuid() en .NET et crypto.randomUUID() en JavaScript produisent tous deux des UUID aléatoires de version 4. Des problèmes d'interopérabilité surviennent pourtant, et ils ont trois origines : la façon dont la valeur est écrite en texte, l'ordre de ses 16 octets et la manière dont SQL Server la compare.
Deux noms pour un seul standard
Les UUID sont issus du Network Computing System d'Apollo et ont été repris dans l'OSF Distributed Computing Environment (DCE). Microsoft a adopté la conception du DCE pour COM et a baptisé ces valeurs globally unique identifiers, d'où le sigle GUID. L'IETF a ensuite publié le format dans la RFC 4122 en juillet 2005, remplacée en mai 2024 par la RFC 9562, la version qui a ajouté les UUID v6, v7 et v8. Les deux RFC indiquent explicitement que les UUID sont aussi appelés GUID.
Un champ garde la trace de cette histoire. Les bits de variante, au début du quatrième groupe, distinguent les UUID RFC, dont ce groupe commence par 8, 9, a ou b, d'une plage réservée à la rétrocompatibilité Microsoft, qui commence par c ou d. Les GUID créés par les versions actuelles de Windows et de .NET utilisent la variante RFC : en pratique, impossible de distinguer les uns des autres.
Les formats texte
En .NET, Guid.ToString() accepte un spécificateur de format. Voici les sorties obtenues pour une même valeur avec .NET 8 :
| Spécificateur | Sortie |
|---|---|
N | f47ac10b58cc4372a5670e02b2c3d479 |
D (par défaut) | 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}} |
Toutes sont en minuscules. Le style en majuscules avec accolades, {F47AC10B-58CC-4372-A567-0E02B2C3D479}, vient du Registre Windows, des identifiants de classe COM et des fichiers de projet Visual Studio, et SQL Server affiche lui aussi les valeurs uniqueidentifier en majuscules. La RFC 4122 demande une sortie en minuscules et une lecture insensible à la casse, et Guid.Parse accepte toutes les formes du tableau.
La règle pratique : ne comparez jamais des GUID comme des chaînes. "{F47AC10B-...}" et "f47ac10b-..." sont deux chaînes différentes pour une même valeur. Analysez les deux côtés avec un parseur, ou normalisez en minuscules sans accolades avant de stocker ou de comparer.
L'ordre des octets : la différence qui fait mal
Stocké sur 16 octets, un UUID RFC est en big-endian : les octets se suivent dans le même ordre que les chiffres hexadécimaux. La structure GUID de Microsoft est différente. Elle est déclarée comme un entier de 32 bits, deux entiers de 16 bits et 8 octets isolés, et sur les machines little-endian, les octets des trois entiers sont stockés à l'envers. Guid.ToByteArray() renvoie exactement cette disposition, et new Guid(byte[]) l'attend :
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
Les 8 derniers octets sont identiques ; les trois premiers groupes sont inversés. SQL Server utilise la même disposition en endianness mixte : convertir un uniqueidentifier en binary(16) donne les octets de ToByteArray().
Le bug apparaît quand un côté écrit les octets dans une disposition et que l'autre les lit dans l'autre. Un service .NET écrit ToByteArray() dans une colonne BINARY(16) ou dans un message binaire ; un service Java, Go ou Python lit ces octets comme un UUID RFC et obtient 0bc17af4-cc58-7243-a567-0e02b2c3d479. La valeur reste unique, donc rien n'échoue, mais les recherches entre les deux systèmes ne trouvent jamais de correspondance. Le chiffre de version a bougé lui aussi : dans cet exemple, la valeur brouillée prétend être de version 7.
Choisissez une disposition et convertissez explicitement à la frontière entre les systèmes :
- .NET 8 et versions ultérieures :
guid.ToByteArray(bigEndian: true)etnew Guid(bytes, bigEndian: true)lisent et écrivent dans l'ordre RFC. - Python :
uuid.UUID(bytes_le=data)lit la disposition Microsoft, etu.bytes_lel'écrit. - Partout : si vous stockez du texte ou un type UUID natif plutôt que des octets bruts, la question ne se pose jamais.
Comment SQL Server trie les uniqueidentifier
SQL Server ne trie pas les GUID dans l'ordre où se lit leur texte. Il compare d'abord le dernier groupe de six octets, puis le quatrième groupe, puis les troisième, deuxième et premier groupes, et à l'intérieur de ces trois premiers groupes, c'est l'octet le plus à droite qui compte le plus. Cette liste est triée dans l'ordre croissant des uniqueidentifier, tel que le produit le type SqlGuid de .NET, qui implémente la même comparaison :
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
Trier les mêmes valeurs en tant que System.Guid dans .NET, ou en tant que chaînes dans n'importe quel langage, donne un ordre différent : 00000000-0000-0000-0000-000000000002 arrive en premier et ff000000-0000-0000-0000-000000000000 en dernier. Deux conséquences en découlent :
- La pagination par clé (keyset) peut sauter ou répéter des lignes si l'application trie elle-même les GUID puis demande à SQL Server les lignes
WHERE id > @last. Laissez la base de données se charger des deux. - UUID v7 n'aide pas dans SQL Server. Une valeur v7 commence par un timestamp, mais SQL Server compare d'abord la fin aléatoire : les clés v7, y compris celles de
Guid.CreateVersion7()dans .NET 9, arrivent à des positions aléatoires dans un indexuniqueidentifier, exactement comme les clés v4.
NEWID vs NEWSEQUENTIALID
NEWID() | NEWSEQUENTIALID() | |
|---|---|---|
| Génération | Aléatoire, version 4 | UuidCreateSequential de Windows, un UUID basé sur le temps, dont les octets sont réarrangés |
| Où l'utiliser | Partout en T-SQL | Uniquement dans une contrainte DEFAULT sur une colonne uniqueidentifier |
| Ordre | Aléatoire | Croissant selon l'ordre de tri de SQL Server sur cette machine ; peut repartir plus bas après un redémarrage de Windows |
| Prévisible | Non | Oui : la valeur suivante peut être devinée |
| Comportement de l'index | Les insertions arrivent n'importe où et provoquent des divisions de pages | Les insertions vont à la fin de l'index |
NEWSEQUENTIALID() existe parce que les clés aléatoires fragmentent les index cluster. C'est le réarrangement des octets qui rend ses valeurs croissantes selon la comparaison inhabituelle de SQL Server, et c'est aussi pourquoi les trier comme du texte ne reproduit pas leur ordre de création. Comme les valeurs peuvent être devinées, la documentation de Microsoft le déconseille lorsque la confidentialité compte : gardez ces ID hors des URL. Si les clés sont plutôt générées dans l'application, reproduire le tri de SQL Server impose de placer les octets temporels dans le dernier groupe, soit l'inverse de ce que fait UUID v7.
Générer l'un ou l'autre
Puisque les valeurs sont les mêmes, tout générateur d'UUID v4 est un générateur de GUID. Le générateur de GUID produit des valeurs aléatoires de version 4 dans le style du Registre, en majuscules avec accolades, et le générateur d'UUID v4 produit le même type de valeur sous la forme RFC en minuscules. Tous deux permettent d'activer ou de retirer les accolades, les majuscules et les tirets, ce qui couvre les formats N, D et B ci-dessus.