UUID vs GUID: o que realmente muda
GUID é o nome que a Microsoft dá ao UUID. As diferenças que aparecem na prática são a formatação do texto, a ordem dos bytes quando o valor é guardado em 16 bytes e a ordenação do SQL Server.
Se você só precisa da resposta curta: GUID e UUID são o mesmo tipo de valor, um identificador de 128 bits normalmente escrito como 32 dígitos hexadecimais em cinco grupos. Guid.NewGuid() no .NET e crypto.randomUUID() no JavaScript produzem, ambos, UUIDs aleatórios de versão 4. Mesmo assim, problemas de interoperabilidade acontecem, e eles vêm de três lugares: como o valor é escrito como texto, em que ordem ficam os seus 16 bytes e como o SQL Server o compara.
Dois nomes para um único padrão
Os UUIDs vêm do Network Computing System da Apollo e foram levados para o Distributed Computing Environment (DCE) da OSF. A Microsoft adotou o projeto do DCE no COM e chamou os valores de globally unique identifiers, os GUIDs. Mais tarde, o IETF publicou o formato como RFC 4122, em julho de 2005, substituída em maio de 2024 pela RFC 9562, a versão que acrescentou os UUIDs v6, v7 e v8. As duas RFCs dizem com todas as letras que UUIDs também são conhecidos como GUIDs.
Um campo guarda essa história de nomes. Os bits de variante no início do quarto grupo distinguem os UUIDs da RFC, em que esse grupo começa com 8, 9, a ou b, de uma faixa reservada para compatibilidade retroativa da Microsoft, que começa com c ou d. Os GUIDs criados pelas versões atuais do Windows e do .NET usam a variante da RFC, então na prática os dois são indistinguíveis.
Formatos de texto
O Guid.ToString() do .NET aceita um especificador de formato. Estas são as saídas para o mesmo valor no .NET 8:
| Especificador | Saída |
|---|---|
N | f47ac10b58cc4372a5670e02b2c3d479 |
D (padrão) | 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ão em minúsculas. O estilo em maiúsculas com chaves, {F47AC10B-58CC-4372-A567-0E02B2C3D479}, vem do Registro do Windows, dos IDs de classe do COM e dos arquivos de projeto do Visual Studio, e o SQL Server também exibe valores uniqueidentifier em maiúsculas. A RFC 4122 pede saída em minúsculas e entrada sem distinção entre maiúsculas e minúsculas, e o Guid.Parse aceita todas as formas da tabela.
A regra prática: nunca compare GUIDs como strings. "{F47AC10B-...}" e "f47ac10b-..." são strings diferentes e o mesmo valor. Faça o parse dos dois lados, ou normalize para minúsculas e sem chaves antes de guardar ou comparar.
Ordem dos bytes: a diferença que causa bugs
Guardado em 16 bytes, um UUID da RFC é big-endian: os bytes vêm na mesma ordem dos dígitos hexadecimais. A estrutura GUID da Microsoft é diferente. Ela é declarada como um inteiro de 32 bits, dois inteiros de 16 bits e 8 bytes avulsos, e em máquinas little-endian os três inteiros são guardados com os bytes invertidos. Guid.ToByteArray() devolve exatamente esse layout, e new Guid(byte[]) espera recebê-lo:
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
Os últimos 8 bytes são idênticos; os três primeiros grupos estão invertidos. O SQL Server usa o mesmo layout misto: converter um uniqueidentifier para binary(16) dá os mesmos bytes de ToByteArray().
O bug aparece quando um lado grava os bytes em um layout e o outro lado os lê no outro. Um serviço .NET grava ToByteArray() em uma coluna BINARY(16) ou em uma mensagem binária; um serviço em Java, Go ou Python lê os bytes como um UUID da RFC e obtém 0bc17af4-cc58-7243-a567-0e02b2c3d479. O valor continua único, então nada falha, mas as buscas entre os dois sistemas nunca encontram correspondência. O dígito de versão também mudou de lugar: neste exemplo, o valor embaralhado diz ser da versão 7.
Escolha um layout e converta explicitamente na fronteira entre os sistemas:
- .NET 8 e posteriores:
guid.ToByteArray(bigEndian: true)enew Guid(bytes, bigEndian: true)leem e gravam na ordem da RFC. - Python:
uuid.UUID(bytes_le=data)lê o layout da Microsoft, eu.bytes_leo grava. - Em qualquer lugar: se você guarda texto ou um tipo UUID nativo em vez de bytes brutos, a questão nem chega a aparecer.
Como o SQL Server ordena uniqueidentifier
O SQL Server não ordena GUIDs do jeito que o texto sugere. Ele compara primeiro o último grupo, de seis bytes, depois o quarto grupo, e em seguida o terceiro, o segundo e o primeiro, e dentro desses três primeiros grupos o byte mais à direita é o que mais pesa. A lista abaixo está em ordem crescente de uniqueidentifier, conforme produzida pelo tipo SqlGuid do .NET, que implementa a mesma comparação:
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
Ordenar os mesmos valores como System.Guid no .NET, ou como strings em qualquer linguagem, dá outra ordem: 00000000-0000-0000-0000-000000000002 vem primeiro e ff000000-0000-0000-0000-000000000000 por último. Isso tem duas consequências:
- A paginação por keyset pode pular ou repetir linhas se a aplicação ordena os GUIDs por conta própria e depois pede ao SQL Server as linhas
WHERE id > @last. Deixe o banco de dados fazer as duas coisas. - O UUID v7 não ajuda no SQL Server. Um valor v7 começa com um timestamp, mas o SQL Server compara primeiro o final aleatório, então chaves v7, incluindo as do
Guid.CreateVersion7()do .NET 9, caem em posições aleatórias de um índiceuniqueidentifier, exatamente como chaves v4.
NEWID vs NEWSEQUENTIALID
NEWID() | NEWSEQUENTIALID() | |
|---|---|---|
| Como é gerado | Aleatório, versão 4 | UuidCreateSequential do Windows, um UUID baseado em tempo, com os bytes reorganizados |
| Onde pode ser usado | Em qualquer ponto do T-SQL | Só em uma restrição DEFAULT de uma coluna uniqueidentifier |
| Ordem | Aleatória | Crescente na ordenação do SQL Server naquela máquina; pode recomeçar de um valor menor depois que o Windows reinicia |
| Previsível | Não | Sim: o próximo valor pode ser adivinhado |
| Comportamento no índice | Os inserts caem em qualquer lugar e causam divisões de página | Os inserts vão para o final do índice |
O NEWSEQUENTIALID() existe porque chaves aleatórias fragmentam índices clusterizados. A reorganização dos bytes é o que faz os seus valores crescerem sob a comparação incomum do SQL Server, e é também por isso que ordená-los como texto não reproduz a ordem em que foram criados. Como os valores podem ser adivinhados, a documentação da Microsoft desaconselha o seu uso quando a privacidade importa; mantenha esses IDs fora das URLs. Se as chaves forem geradas na aplicação, ordenar como o SQL Server exige colocar os bytes baseados em tempo no último grupo, o oposto do que o UUID v7 faz.
Gerar um ou outro
Como os valores são os mesmos, qualquer gerador de UUID v4 é um gerador de GUID. O gerador de GUID produz valores aleatórios de versão 4 no estilo do Registro, em maiúsculas e com chaves, e o gerador de UUID v4 produz o mesmo tipo de valor em minúsculas, no formato da RFC. Os dois permitem ligar ou desligar chaves, maiúsculas e hífens, o que cobre os formatos N, D e B mostrados acima.