UUID vs GUID: qué es realmente distinto
GUID es el nombre que Microsoft da al UUID. Las diferencias que encontrará en la práctica son el formato de texto, el orden de los bytes cuando el valor se guarda como 16 bytes y la forma en que SQL Server los ordena.
Si solo necesita la respuesta corta: un GUID y un UUID son el mismo tipo de valor, un identificador de 128 bits que normalmente se escribe como 32 dígitos hexadecimales repartidos en cinco grupos. Guid.NewGuid() en .NET y crypto.randomUUID() en JavaScript producen UUID aleatorios de la versión 4. Aun así, surgen problemas de interoperabilidad, y vienen de tres sitios: cómo se escribe el valor como texto, en qué orden van sus 16 bytes y cómo lo compara SQL Server.
Dos nombres para un mismo estándar
Los UUID proceden del Network Computing System de Apollo y pasaron después al Distributed Computing Environment (DCE) de OSF. Microsoft adoptó el diseño de DCE para COM y llamó a estos valores identificadores globalmente únicos (globally unique identifiers). Más tarde, el IETF publicó el formato como RFC 4122 en julio de 2005, sustituido en mayo de 2024 por el RFC 9562, la versión que añadió UUID v6, v7 y v8. Ambos RFC dicen expresamente que los UUID también se conocen como GUID.
Hay un campo que conserva la historia del nombre. Los bits de variante al principio del cuarto grupo distinguen los UUID del RFC, en los que ese grupo empieza por 8, 9, a o b, de un rango reservado para la compatibilidad con versiones anteriores de Microsoft, que empieza por c o d. Los GUID que generan las versiones actuales de Windows y .NET usan la variante del RFC, así que en la práctica son indistinguibles.
Formatos de texto
El método Guid.ToString() de .NET acepta un especificador de formato. Estas son las salidas para un mismo valor en .NET 8:
| Especificador | Salida |
|---|---|
N | f47ac10b58cc4372a5670e02b2c3d479 |
D (por defecto) | 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}} |
Todas están en minúsculas. El estilo en mayúsculas y con llaves, {F47AC10B-58CC-4372-A567-0E02B2C3D479}, procede del registro de Windows, de los identificadores de clase COM y de los archivos de proyecto de Visual Studio, y SQL Server también muestra los valores uniqueidentifier en mayúsculas. El RFC 4122 pide generar la salida en minúsculas y aceptar la entrada sin distinguir mayúsculas de minúsculas, y Guid.Parse acepta todas las formas de la tabla.
La regla práctica: nunca compare GUID como cadenas. "{F47AC10B-...}" y "f47ac10b-..." son cadenas distintas y, a la vez, el mismo valor. Analice ambos lados, o normalícelos a minúsculas y sin llaves antes de guardarlos o compararlos.
Orden de bytes: la diferencia que da problemas
Guardado como 16 bytes, un UUID del RFC es big-endian: los bytes van en el mismo orden que los dígitos hexadecimales. La estructura GUID de Microsoft es distinta. Se declara como un entero de 32 bits, dos enteros de 16 bits y 8 bytes sueltos, y en las máquinas little-endian los tres enteros se guardan con los bytes invertidos. Guid.ToByteArray() devuelve exactamente esa disposición, y new Guid(byte[]) es la que espera:
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
Los últimos 8 bytes son idénticos; los tres primeros grupos están invertidos. SQL Server usa la misma disposición de orden mixto: convertir un uniqueidentifier a binary(16) da los mismos bytes que ToByteArray().
El fallo aparece cuando un lado escribe los bytes con una disposición y el otro los lee con la otra. Un servicio .NET escribe ToByteArray() en una columna BINARY(16) o en un mensaje binario; un servicio en Java, Go o Python lee esos bytes como un UUID del RFC y obtiene 0bc17af4-cc58-7243-a567-0e02b2c3d479. El valor sigue siendo único, así que nada falla, pero las búsquedas entre los dos sistemas nunca coinciden. También se ha movido el dígito de versión: en este ejemplo, el valor desordenado afirma ser de la versión 7.
Elija una disposición y convierta de forma explícita en la frontera entre sistemas:
- .NET 8 y posteriores:
guid.ToByteArray(bigEndian: true)ynew Guid(bytes, bigEndian: true)leen y escriben en el orden del RFC. - Python:
uuid.UUID(bytes_le=data)lee la disposición de Microsoft, yu.bytes_lela escribe. - En cualquier entorno: si guarda texto o un tipo UUID nativo en lugar de bytes en bruto, el problema ni siquiera se plantea.
Cómo ordena SQL Server los uniqueidentifier
SQL Server no ordena los GUID tal como se leen en texto. Compara primero el último grupo de seis bytes, luego el cuarto grupo y después el tercero, el segundo y el primero, y dentro de esos tres primeros grupos pesa más el byte situado más a la derecha. Esta lista está en orden ascendente de uniqueidentifier, tal como la produce el tipo SqlGuid de .NET, que implementa la misma comparación:
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
Si se ordenan los mismos valores como System.Guid en .NET, o como cadenas en cualquier lenguaje, el resultado es otro: 00000000-0000-0000-0000-000000000002 queda el primero y ff000000-0000-0000-0000-000000000000, el último. De ahí se derivan dos consecuencias:
- La paginación por keyset puede saltarse o repetir filas si la aplicación ordena los GUID por su cuenta y luego pide a SQL Server las filas
WHERE id > @last. Deje que la base de datos se encargue de ambas cosas. - UUID v7 no ayuda en SQL Server. Un valor v7 empieza con una marca de tiempo, pero SQL Server compara primero la cola aleatoria, así que las claves v7, incluidas las de
Guid.CreateVersion7()de .NET 9, caen en posiciones aleatorias de un índiceuniqueidentifier, igual que las claves v4.
NEWID vs NEWSEQUENTIALID
NEWID() | NEWSEQUENTIALID() | |
|---|---|---|
| Cómo se genera | Aleatorio, versión 4 | UuidCreateSequential de Windows, un UUID basado en el tiempo, con los bytes reordenados |
| Dónde se puede usar | En cualquier parte de T-SQL | Solo en una restricción DEFAULT de una columna uniqueidentifier |
| Orden | Aleatorio | Creciente según el orden de SQL Server en esa máquina; puede volver a empezar desde valores menores tras reiniciar Windows |
| Predecible | No | Sí: el siguiente valor se puede adivinar |
| Comportamiento en el índice | Las inserciones caen en cualquier parte y provocan divisiones de página | Las inserciones van al final del índice |
NEWSEQUENTIALID() existe porque las claves aleatorias fragmentan los índices agrupados (clustered). La reordenación de bytes es lo que hace que sus valores crezcan bajo la peculiar comparación de SQL Server, y también la razón por la que ordenarlos como texto no reproduce el orden en que se crearon. Como los valores se pueden adivinar, la documentación de Microsoft desaconseja usarla cuando la privacidad importa; mantenga esos IDs fuera de las URL. Si las claves se generan en la aplicación, ordenarlas como lo hace SQL Server exige colocar los bytes basados en el tiempo en el último grupo, justo lo contrario de lo que hace UUID v7.
Generar uno u otro
Como los valores son los mismos, cualquier generador de UUID v4 es también un generador de GUID. El generador de GUID produce valores aleatorios de la versión 4 al estilo del registro de Windows, en mayúsculas y con llaves, y el generador de UUID v4 produce el mismo tipo de valor en la forma del RFC, en minúsculas. Los dos permiten activar o quitar las llaves, las mayúsculas y los guiones, lo que cubre los formatos N, D y B de la tabla anterior.