Como devo armazenar o GUID nas tabelas MySQL?

146

Eu uso o varchar (36) ou existem maneiras melhores de fazer isso?

CDR
fonte
1
"thaBadDawg" oferece uma boa resposta. Há um encadeamento paralelo no estouro de pilha que discute o tópico. Eu adicionei alguns comentários aos tópicos que respondem ao link para recursos com mais detalhes. Aqui está o link da pergunta: stackoverflow.com/questions/547118/storing-mysql-guid-uuids - Espero que esse tópico se torne mais comum quando as pessoas começarem a considerar a AWS e o Aurora.
Zack Jannsen

Respostas:

104

Meu DBA me perguntou quando perguntei sobre a melhor maneira de armazenar GUIDs para meus objetos por que eu precisava armazenar 16 bytes quando eu poderia fazer a mesma coisa em 4 bytes com um Número Inteiro. Desde que ele colocou esse desafio para mim, pensei que agora era um bom momento para mencioná-lo. Dito isto ...

Você pode armazenar um guia como um binário CHAR (16) se desejar fazer o melhor uso possível do espaço de armazenamento.

thaBadDawg
fonte
176
Porque com 16 bytes, você pode gerar coisas em diferentes bancos de dados, em máquinas diferentes, em momentos diferentes, e ainda mesclar os dados em conjunto sem problemas :)
Billy ONeal
4
precisa de resposta, o que realmente é um binário de 16 caracteres? não char? não é binário? Eu não vejo esse tipo em nenhuma das ferramentas mysql GUI, nem em qualquer documentação no site mysql. @BillyONeal
nawfal
3
@nawfal: Char é o tipo de dados. BINARY é o especificador de tipo em relação ao tipo. O único efeito que ele tem é modificar a forma como o MySQL faz a intercalação. Consulte dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html para obter mais detalhes. Obviamente, você pode apenas usar um tipo BINARY diretamente se a sua ferramenta de edição de banco de dados permitir. (Ferramentas mais velhos não sabem do tipo de dados binários, mas sei da bandeira coluna binária)
Billy ONeal
2
um CHAR e um campo BINARY são essencialmente os mesmos. Se você quiser levá-lo ao nível mais básico, um CHAR é um campo binário que espera um valor de 0 a 255 com a intenção de representar o referido valor com um valor mapeado de uma tabela de pesquisa (na maioria dos casos agora, UTF8). Um campo BINARY espera o mesmo tipo de valor sem qualquer intenção de representar os dados a partir de uma tabela de pesquisa. Eu usei o CHAR (16) nos dias 4.x porque naquela época o MySQL não era tão bom quanto é agora.
ThaBadDawg
15
Existem várias razões pelas quais um GUID é muito melhor que um incremento automático. Jeff Atwood lista estes . Para mim, a melhor vantagem do uso de um GUID é que meu aplicativo não precisará de uma ida e volta ao banco de dados para conhecer a chave de uma entidade: eu poderia preenchê-lo programaticamente, o que não seria possível se estivesse usando um campo de incremento automático. Isso me salvou de várias dores de cabeça: com o GUID, posso gerenciar a entidade da mesma maneira, independentemente da entidade já ter sido persistida ou de uma nova.
Arialdo Martini
48

Eu o armazenaria como um caractere (36).

Brian Fisher
fonte
5
Não vejo por que você deveria guardar -s.
Afshin Mehrabani
2
@AfshinMehrabani É simples, direto, legível por humanos. Não é necessário, é claro, mas se o armazenamento desses bytes extras não prejudicar, essa é a melhor solução.
user1717828
2
Armazenar os traços pode não ser uma boa ideia, pois causará mais sobrecarga. Se você deseja torná-lo legível por humanos, faça o aplicativo ler com os traços.
Lucca Ferri
@AfshinMehrabani outra consideração é analisá-lo no banco de dados. A maioria das implementações espera traços em um guia válido.
Ryan Gates
Você pode inserir os hífens ao buscar para converter um char (32) em char (36) facilmente. use o Insert FN do mySql.
joedotnot 25/01
33

Acrescentando à resposta de ThaBadDawg, use essas funções úteis (graças a um colega mais sábio) para obter uma sequência de 36 caracteres de comprimento e uma matriz de 16 bytes.

DELIMITER $$

CREATE FUNCTION `GuidToBinary`(
    $Data VARCHAR(36)
) RETURNS binary(16)
DETERMINISTIC
NO SQL
BEGIN
    DECLARE $Result BINARY(16) DEFAULT NULL;
    IF $Data IS NOT NULL THEN
        SET $Data = REPLACE($Data,'-','');
        SET $Result =
            CONCAT( UNHEX(SUBSTRING($Data,7,2)), UNHEX(SUBSTRING($Data,5,2)),
                    UNHEX(SUBSTRING($Data,3,2)), UNHEX(SUBSTRING($Data,1,2)),
                    UNHEX(SUBSTRING($Data,11,2)),UNHEX(SUBSTRING($Data,9,2)),
                    UNHEX(SUBSTRING($Data,15,2)),UNHEX(SUBSTRING($Data,13,2)),
                    UNHEX(SUBSTRING($Data,17,16)));
    END IF;
    RETURN $Result;
END

$$

CREATE FUNCTION `ToGuid`(
    $Data BINARY(16)
) RETURNS char(36) CHARSET utf8
DETERMINISTIC
NO SQL
BEGIN
    DECLARE $Result CHAR(36) DEFAULT NULL;
    IF $Data IS NOT NULL THEN
        SET $Result =
            CONCAT(
                HEX(SUBSTRING($Data,4,1)), HEX(SUBSTRING($Data,3,1)),
                HEX(SUBSTRING($Data,2,1)), HEX(SUBSTRING($Data,1,1)), '-', 
                HEX(SUBSTRING($Data,6,1)), HEX(SUBSTRING($Data,5,1)), '-',
                HEX(SUBSTRING($Data,8,1)), HEX(SUBSTRING($Data,7,1)), '-',
                HEX(SUBSTRING($Data,9,2)), '-', HEX(SUBSTRING($Data,11,6)));
    END IF;
    RETURN $Result;
END
$$

CHAR(16)é realmente um BINARY(16), escolha seu sabor preferido

Para seguir melhor o código, use o exemplo, conforme o GUID ordenado por dígitos abaixo. (Caracteres ilegais são usados ​​para fins ilustrativos - cada um possui um caractere exclusivo.) As funções transformarão a ordem dos bytes para obter uma ordem de bits para um cluster de índice superior. O guia reordenado é mostrado abaixo do exemplo.

12345678-9ABC-DEFG-HIJK-LMNOPQRSTUVW
78563412-BC9A-FGDE-HIJK-LMNOPQRSTUVW

Traços removidos:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW
KCD
fonte
Aqui está o GuidToBinary acima, sem remover os hífens da string: CREATE FUNCTION GuidToBinary($ guid char (36)) RETORNA binário (16) RETURN CONCAT (UNHEX (SUBSTRING ($ guid, 7, 2)), UNHEX (SUBSTRING ($ guid, 5, 2)), UNHEX (SUBSTRING ($ guid, 3, 2)), UNHEX (SUBSTRING ($ guid, 1, 2)), UNHEX (SUBSTRING ($ guid, 12, 2)), UNHEX (SUBSTRING ($ guid, 10, 2)), UNHEX (SUBSTRING ($ guid, 17, 2)), UNHEX (SUBSTRING ($ guid, 15, 2)), UNHEX (SUBSTRING ($ guid, 20, 4)), UNHEX (SUBSTRING ($ guid, 25, 12)));
Jonathan Oliver
4
Para os curiosos, essas funções são superiores a apenas UNHEX (REPLACE (UUID (), '-', '')) porque organiza os bits em uma ordem que terá melhor desempenho em um índice clusterizado.
Slashterix
Isto é muito útil, mas eu sinto que poderia ser melhorado com uma fonte CHARe BINARYde equivalência ( os docs parecem implicar há diferenças importantes e uma explicação do desempenho do índice porque agrupado é melhor com bytes reordenadas.
Patrick M
Quando eu uso isso, meu guia é alterado. Tentei inseri-lo usando unhex (replace (string, '-', '')) e a função acima e quando os converto novamente usando os mesmos métodos, o guid selecionado não é aquele que foi inserido. O que está transformando o guia? Tudo o que fiz foi copiar o código de cima.
Vsdev 17/12/2015
@JonathanOliver Você poderia compartilhar o código da função BinaryToGuid ()?
Arun Avanathan
27

char (36) seria uma boa escolha. Também é possível usar a função UUID () do MySQL, que retorna um formato de texto de 36 caracteres (hexadecimal com hífens) que pode ser usado para recuperar esses IDs do banco de dados.

Aprendendo
fonte
19

"Melhor" depende do que você está otimizando.

Quanto você se importa com tamanho / desempenho de armazenamento versus facilidade de desenvolvimento? Mais importante: você está gerando GUIDs suficientes ou buscando-os com frequência suficiente para que isso importe?

Se a resposta for "não", char(36)é mais do que suficiente e torna os GUIDs de armazenamento / busca simples. Caso contrário, binary(16)é razoável, mas você terá que se apoiar no MySQL e / ou na sua linguagem de programação preferida para converter a partir da representação usual de strings.

candu
fonte
2
Se você hospeda o software (por exemplo, uma página da web, por exemplo) e não vende / instala no cliente, sempre pode começar com char (36) para facilitar o desenvolvimento no estágio inicial do software e mudar para um formato mais compacto formato à medida que o sistema cresce em uso e começa a precisar de otimização.
Xavi Montero
1
A maior desvantagem do caractere muito maior (36) é a quantidade de espaço que o índice ocupará. Se você possui um grande número de registros no banco de dados, está dobrando o tamanho do índice.
bpeikes
8

Binário (16) seria bom, melhor do que o uso de varchar (32).

Onkar Janwa
fonte
7

A rotina GuidToBinary postada pelo KCD deve ser ajustada para levar em conta o layout de bit do registro de data e hora na string GUID. Se a string representa um UUID da versão 1, como aqueles retornados pela rotina mysql uuid (), os componentes de tempo são incorporados nas letras 1-G, excluindo o D.

12345678-9ABC-DEFG-HIJK-LMNOPQRSTUVW
12345678 = least significant 4 bytes of the timestamp in big endian order
9ABC     = middle 2 timestamp bytes in big endian
D        = 1 to signify a version 1 UUID
EFG      = most significant 12 bits of the timestamp in big endian

Ao converter para binário, a melhor ordem para indexação seria: EFG9ABC12345678D + o restante.

Você não deseja trocar 12345678 por 78563412 porque o big endian já produz a melhor ordem de bytes de índice binário. No entanto, você deseja que os bytes mais significativos sejam movidos na frente dos bytes inferiores. Portanto, o EFG vai primeiro, seguido pelos bits do meio e pelos inferiores. Gere uma dúzia de UUIDs com uuid () ao longo de um minuto e você deverá ver como essa ordem gera a classificação correta.

select uuid(), 0
union 
select uuid(), sleep(.001)
union 
select uuid(), sleep(.010)
union 
select uuid(), sleep(.100)
union 
select uuid(), sleep(1)
union 
select uuid(), sleep(10)
union
select uuid(), 0;

/* output */
6eec5eb6-9755-11e4-b981-feb7b39d48d6
6eec5f10-9755-11e4-b981-feb7b39d48d6
6eec8ddc-9755-11e4-b981-feb7b39d48d6
6eee30d0-9755-11e4-b981-feb7b39d48d6
6efda038-9755-11e4-b981-feb7b39d48d6
6f9641bf-9755-11e4-b981-feb7b39d48d6
758c3e3e-9755-11e4-b981-feb7b39d48d6 

Os dois primeiros UUIDs foram gerados mais próximos no tempo. Eles variam apenas nos últimos 3 petiscos do primeiro bloco. Esses são os bits menos significativos do registro de data e hora, o que significa que queremos empurrá-los para a direita quando convertemos isso em uma matriz de bytes indexáveis. Como um exemplo contrário, o último ID é o mais atual, mas o algoritmo de troca do KCD o colocaria antes do 3º ID (3e antes de dc, últimos bytes do primeiro bloco).

A ordem correta para a indexação seria:

1e497556eec5eb6... 
1e497556eec5f10... 
1e497556eec8ddc... 
1e497556eee30d0... 
1e497556efda038... 
1e497556f9641bf... 
1e49755758c3e3e... 

Consulte este artigo para obter informações de suporte: http://mysql.rjweb.org/doc.php/uuid

*** observe que não divido a mordidela da versão dos 12 bits mais altos do carimbo de data e hora. Esta é a mordidela D do seu exemplo. Eu apenas jogo na frente. Portanto, minha sequência binária acaba sendo DEFG9ABC e assim por diante. Isso implica que todos os meus UUIDs indexados começam com a mesma mordidela. O artigo faz a mesma coisa.

bigh_29
fonte
Qual é o objetivo disso para economizar espaço de armazenamento? ou para torná-los úteis?
MD004
1
@ MD004. Ele cria um melhor índice de classificação. O espaço permanece o mesmo.
bigh_29
5

Para aqueles que apenas tropeçam nisso, agora existe uma alternativa muito melhor conforme a pesquisa da Percona.

Consiste em reorganizar os chunks UUID para obter uma indexação ideal e depois converter em binário para reduzir o armazenamento.

Leia o artigo completo aqui

sonolento
fonte
Eu li esse artigo antes. Acho muito interessante, mas como devemos fazer uma consulta se quisermos filtrar por um ID que é binário? Acho que precisamos nos azarar novamente e depois aplicar os critérios. Isso é tão exigente? Por que armazenar binário (16) (com certeza é melhor que varchar (36)) em vez de bigint de 8 bytes?
Maximus Decimus
2
Há um artigo atualizado de MariaDB que deve responder a sua pergunta mariadb.com/kb/en/mariadb/guiduuid-performance
sleepycal
fwiw, o UUIDv4 é completamente aleatório e não precisa de chunking.
Mahmoud Al-Qudsi
2

Eu sugeriria o uso das funções abaixo, pois as mencionadas por @ bigh_29 transformam meus guias em novos (por razões que não entendo). Além disso, estes são um pouco mais rápidos nos testes que fiz nas minhas mesas. https://gist.github.com/damienb/159151

DELIMITER |

CREATE FUNCTION uuid_from_bin(b BINARY(16))
RETURNS CHAR(36) DETERMINISTIC
BEGIN
  DECLARE hex CHAR(32);
  SET hex = HEX(b);
  RETURN LOWER(CONCAT(LEFT(hex, 8), '-', MID(hex, 9,4), '-', MID(hex, 13,4), '-', MID(hex, 17,4), '-', RIGHT(hex, 12)));
END
|

CREATE FUNCTION uuid_to_bin(s CHAR(36))
RETURNS BINARY(16) DETERMINISTIC
RETURN UNHEX(CONCAT(LEFT(s, 8), MID(s, 10, 4), MID(s, 15, 4), MID(s, 20, 4), RIGHT(s, 12)))
|

DELIMITER ;
vsdev
fonte
-4

se você tiver um valor char / varchar formatado como o GUID padrão, poderá simplesmente armazená-lo como BINARY (16) usando o CAST simples (MyString AS BINARY16), sem todas essas sequências impressionantes de CONCAT + SUBSTR.

Os campos BINARY (16) são comparados / classificados / indexados muito mais rapidamente que as strings e também ocupam duas vezes menos espaço no banco de dados

George Hazan
fonte
2
A execução desta consulta mostra que o CAST converte a string uuid em bytes ASCII: set @a = uuid (); selecione @a, hex (converter (@a AS BINARY (16))); Eu recebo 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (espaços adicionados para formatação). 0x31 = ASCII 1, 0x36 = ASCII 6. Chegamos até 0x2D, ​​que é o hífen. Isso não é muito diferente do que apenas armazenar o guia como uma sequência, exceto que você trunca a sequência no 16º caractere, o que separa a parte do ID que é específico da máquina.
bigh_29
Sim, isso é simplesmente truncamento. select CAST("hello world, this is as long as uiid" AS BINARY(16));produzhello world, thi
MD004 22/02