Existe uma penalidade por usar BINARY (16) em vez de UNIQUEIDENTIFIER?

19

Recentemente, herdei um banco de dados do SQL Server que usa em BINARY(16)vez de UNIQUEIDENTIFIERarmazenar Guids. Faz isso para tudo, incluindo chaves primárias.

Devo me preocupar?

Jonathan Allen
fonte
Ele usa binário (16) de forma consistente o tempo todo? Incluindo variáveis ​​e parâmetros? Caso contrário, você precisa considerar os efeitos de lançamentos implícitos.
Martin Smith
Sim, felizmente também não tenho que lidar com elencos implícitos.
Jonathan Allen

Respostas:

21

Devo me preocupar?

Bem, há algumas coisas aqui que são um pouco preocupantes.

Primeiro: embora seja verdade que a UNIQUEIDENTIFIER(ie Guid) seja um valor binário de 16 bytes, também é verdade que:

  1. Todos os dados podem ser armazenados em formato binário (por exemplo, INTpodem ser armazenados BINARY(4), DATETIMEpodem ser armazenados BINARY(8)etc), portanto, o nº 2 ↴
  2. Provavelmente, existe um motivo para ter um tipo de dados separado para GUIDs fora da mera conveniência (por exemplo, sysnamecomo um alias para NVARCHAR(128)).

As três diferenças comportamentais que posso encontrar são:

  • Comparar UNIQUEIDENTIFIERvalores no SQL Server, para o bem ou para o mal, na verdade não é feito da mesma maneira que comparar BINARY(16)valores. De acordo com a página do MSDN para comparar valores GUID e uniqueidentifier , ao comparar UNIQUEIDENTIFIERvalores no SQL Server:

    os últimos seis bytes de um valor são mais significativos

  • Embora esses valores não sejam classificados com frequência, há uma pequena diferença entre esses dois tipos. De acordo com a página do MSDN para uniqueidentifier :

    a ordenação não é implementada comparando os padrões de bits dos dois valores.

  • Como existem diferenças na maneira como os valores GUID são manipulados entre o SQL Server e o .NET (observados na página "Comparando GUID e valores exclusivos do identificador", acima), extrair esses dados do SQL Server para o código do aplicativo pode não ser tratado adequadamente em o código do aplicativo, se for necessário emular o comportamento de comparação do SQL Server. Esse comportamento pode ser emulado através da conversão para a SqlGuid, mas um desenvolvedor saberia fazer isso?

Segundo: baseado na seguinte declaração

Faz isso para tudo, incluindo chaves primárias.

Em geral, eu me preocuparia com o desempenho do sistema usando GUIDs como PKs em vez de como Chaves Alternativas, além de usar um INTou mesmo BIGINTcomo PK. E ainda mais preocupado se essas PKs de GUID são os índices agrupados.

ATUALIZAR

O comentário a seguir, feito pelo OP na resposta de @ Rob, traz uma preocupação adicional:

foi migrado do MySQL

Os GUIDs podem ser armazenados em 2 formatos binários diferentes . Portanto, pode haver motivos de preocupação, dependendo de:

  1. em que sistema a representação binária foi gerada e
  2. se os valores da string foram usados ​​fora do sistema original, como no código do aplicativo ou dados aos clientes para uso em arquivos de importação, etc.

O problema com o qual a representação binária foi gerada tem a ver com a ordem de bytes dos 3 primeiros dos 4 "campos". Se você seguir o link acima para o artigo da Wikipedia, verá que o RFC 4122 especifica o uso da codificação "Big Endian" para todos os 4 campos, mas os GUIDs da Microsoft especificam o uso do Endianness "Nativo". Bem, a arquitetura da Intel é Little Endian, portanto, a ordem de bytes para os três primeiros campos é revertida dos sistemas após o RFC (assim como os GUIDs no estilo da Microsoft gerados nos sistemas Big Endian). O primeiro campo, "Dados 1", é de 4 bytes. Em um Endianness seria representado como (hipoteticamente) 0x01020304. Mas no outro Endianness seria 0x04030201. Então, se o banco de dados atual 'BINARY(16)essa representação binária foi gerada em um sistema após a RFC e, em seguida, converter os dados atualmente no BINARY(16)campo em UNIQUEIDENTIFIERresultará em um GUID diferente do que foi criado originalmente. Isso realmente não representa um problema se os valores nunca saírem do banco de dados e os valores são sempre comparados para igualdade e não para ordem.

A preocupação com o pedido é simplesmente que eles não estarão na mesma ordem após a conversão para UNIQUEIDENTIFIER. Felizmente, se o sistema original realmente era o MySQL, o pedido nunca foi feito na representação binária em primeiro lugar, já que o MySQL possui apenas uma representação de string do UUID .

A preocupação com os valores das strings sendo usadas fora do banco de dados é mais séria, novamente, se a representação binária foi gerada fora do Windows / SQL Server. Como a ordem dos bytes é potencialmente diferente, o mesmo GUID na forma de sequência resultaria em 2 representações binárias diferentes, dependendo de onde a conversão ocorreu. Se o código do aplicativo ou os clientes receberem um GUID em forma de sequência como ABCproveniente de uma forma binária de 123 e a representação binária for gerada em um sistema após a RFC, a mesma representação binária (ou seja 123) será convertida em uma forma de sequência DEFquando convertida em a UNIQUEIDENTIFIER. Da mesma forma, a forma original da string ABCseria convertida em uma forma binária de 456quando convertida em a UNIQUEIDENTIFIER.

Portanto, se os GUIDs nunca saírem do banco de dados, não haverá muito o que se preocupar fora do pedido. Ou, se a importação do MySQL foi feita convertendo a forma de string (ou seja FCCEC3D8-22A0-4C8A-BF35-EC18227C9F40), isso pode estar ok. Senão, se esses GUIDs foram fornecidos aos clientes ou no código do aplicativo, você pode testar para ver como eles convertem obtendo um e convertendo via SELECT CONVERT(UNIQUEIDENTIFIER, 'value found outside of the database');e veja se você encontra o registro esperado. Se você não conseguir corresponder os registros, talvez seja necessário manter os campos como BINARY(16).

Com toda a probabilidade, não haverá um problema, mas estou mencionando isso porque, nas condições certas, pode haver um problema.

E como novos GUIDs são inseridos? Gerado no código do aplicativo?

ATUALIZAÇÃO 2

Se a explicação anterior do possível problema relacionado à importação de representações binárias de GUID geradas em outro sistema foi um pouco (ou muito) confusa, espero que o seguinte seja um pouco mais claro:

DECLARE @GUID UNIQUEIDENTIFIER = NEWID();
SELECT @GUID AS [String], CONVERT(BINARY(16), @GUID) AS [Binary];
-- String = 5FED23BE-E52C-40EE-8F45-49664C9472FD
-- Binary = 0xBE23ED5F2CE5EE408F4549664C9472FD
--          BE23ED5F-2CE5-EE40-8F45-49664C9472FD

Na saída mostrada acima, os valores "String" e "Binary" são do mesmo GUID. O valor abaixo da linha "Binário" é o mesmo valor da linha "Binário", mas formatado no mesmo estilo da linha "String" (ou seja, removido "0x" e adicionado os quatro traços). Comparando o primeiro e o terceiro valores, eles não são exatamente iguais, mas são muito próximos: as duas seções mais à direita são idênticas, mas as três seções mais à esquerda não são. Mas se você olhar atentamente, poderá ver que são os mesmos bytes em cada uma das três seções, apenas em uma ordem diferente. Pode ser mais fácil ver se mostro apenas as três primeiras seções e numero os bytes, para que seja mais fácil ver como a ordem deles difere entre as duas representações:

String = 1 5F 2 ED 3 23 4 BE - 5 E5 6 2C - 7 40 8 EE
Binário = 4 BE 3 23 2 ED 1 5F - 6 2C 5 E5 - 8 EE 7 40 (no Windows / SQL Server)

Portanto, em cada agrupamento, a ordem dos bytes é revertida, mas apenas no Windows e também no SQL Server. No entanto, em um sistema que adere à RFC, a representação binária espelhará a representação sting porque não haveria reversão da ordem dos bytes.

Como os dados foram trazidos para o SQL Server do MySQL? Aqui estão algumas opções:

SELECT CONVERT(BINARY(16), '5FED23BE-E52C-40EE-8F45-49664C9472FD'),
       CONVERT(BINARY(16), 0x5FED23BEE52C40EE8F4549664C9472FD),
    CONVERT(BINARY(16), CONVERT(UNIQUEIDENTIFIER, '5FED23BE-E52C-40EE-8F45-49664C9472FD'));

Devoluções:

0x35464544323342452D453532432D3430  
0x5FED23BEE52C40EE8F4549664C9472FD  
0xBE23ED5F2CE5EE408F4549664C9472FD

Supondo que fosse binário a binário direto (ou seja, o Convert # 2 acima), o GUID resultante, se convertido em um real UNIQUEIDENTIFIER, seria:

SELECT CONVERT(UNIQUEIDENTIFIER, 0x5FED23BEE52C40EE8F4549664C9472FD);

Devoluções:

BE23ED5F-2CE5-EE40-8F45-49664C9472FD

O que está errado. E isso nos deixa com três perguntas:

  1. Como os dados foram importados para o SQL Server?
  2. Em qual idioma o código do aplicativo está escrito?
  3. Em qual plataforma o código do aplicativo está sendo executado?
Solomon Rutzky
fonte
Eu diria que os GUIDs são gerados no aplicativo, pois não os vejo no banco de dados.
Jonathan Allen
Não posso dizer que sigo completamente a explicação sobre a ordenação de bytes, mas isso está me fazendo pensar na indexação. O identificador exclusivo teria mais ou menos probabilidade de resultar em fragmentação do índice do que o binário?
Jonathan Allen
2
@ JonathanAllen Adicionei outra seção UPDATE para explicar melhor. E não, a indexação não deve ser diferente entre eles.
Solomon Rutzky
"Felizmente", o SQL Server não altera a ordem entre a variante 1 e a variante 2 - mesmo que 'possa' ser armazenado de maneira diferente no disco, é a mesma ordem confusa de forma consistente.
user2864740
5

Você sempre pode estar preocupado. ;)

O sistema pode ter sido migrado de outro sistema que não suporta identificador exclusivo. Existem outros compromissos que você não conhece?

O designer pode não ter conhecimento sobre o tipo de identificador exclusivo. Que outras coisas eles não sabiam?

Tecnicamente, porém - não deve ser uma grande preocupação.

Rob Farley
fonte
Sim, foi migrado do MySQL. E sim, há muitas ... coisas interessantes para se olhar.
Jonathan Allen