¿Cómo debo almacenar el GUID en las tablas de MySQL?

146

¿Utilizo varchar (36) o hay mejores formas de hacerlo?

CDR
fuente
1
"thaBadDawg" ofrece una buena respuesta. Hay un hilo paralelo en Stack Overflow que trata el tema. Agregué algunos comentarios a los hilos que responden a ese enlace a los recursos con más detalle. Aquí está el enlace de la pregunta: stackoverflow.com/questions/547118/storing-mysql-guid-uuids : espero que este tema se vuelva más común cuando la gente empiece a considerar AWS y Aurora.
Zack Jannsen

Respuestas:

104

Mi DBA me preguntó cuando le pregunté sobre la mejor manera de almacenar GUID para mis objetos por qué necesitaba almacenar 16 bytes cuando podía hacer lo mismo en 4 bytes con un número entero. Desde que me presentó ese desafío, pensé que ahora era un buen momento para mencionarlo. Habiendo dicho eso...

Puede almacenar un guid como un binario CHAR (16) si desea aprovechar al máximo el espacio de almacenamiento.

thaBadDawg
fuente
176
Debido a que con 16 bytes, puede generar cosas en diferentes bases de datos, en diferentes máquinas, en diferentes momentos, y aun así combinar los datos sin problemas :)
Billy ONeal
44
necesita respuesta, ¿qué es realmente un binario char 16? no char? no binario? No veo ese tipo en ninguna de las herramientas de mysql gui, ni ninguna documentación en el sitio mysql. @BillyONeal
nawfal
3
@nawfal: Char es el tipo de datos. BINARY es el especificador de tipo contra el tipo. El único efecto que tiene es modificar cómo MySQL hace la recopilación. Consulte dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html para obtener más detalles. Por supuesto, puede usar un tipo BINARIO directamente si la herramienta de edición de su base de datos le permite hacerlo. (Las herramientas más antiguas no conocen el tipo de datos binarios, pero sí conocen el indicador de columna binaria)
Billy ONeal
2
un campo CHAR y BINARY son esencialmente lo mismo. Si desea llevarlo al nivel más básico, un CHAR es un campo binario que espera un valor de 0 a 255 con la intención de representar dicho valor con un valor mapeado de una tabla de búsqueda (en la mayoría de los casos ahora, UTF8). Un campo BINARIO espera el mismo tipo de valor sin ninguna intención de representar dichos datos de una tabla de búsqueda. Usé CHAR (16) en los días 4.x porque en aquel entonces MySQL no era tan bueno como lo es ahora.
thaBadDawg
15
Hay varias buenas razones por las cuales un GUID es mucho mejor que un autoincremento. Jeff Atwood enumera estos . Para mí, la mejor ventaja al usar un GUID es que mi aplicación no necesitará una base de datos de ida y vuelta para conocer la clave de una entidad: podría completarla mediante programación, lo que no podría hacer si estuviera usando un campo de incremento automático. Esto me salvó de varios dolores de cabeza: con GUID puedo administrar la entidad de la misma manera, independientemente de que la entidad ya haya persistido o sea una nueva.
Arialdo Martini
48

Lo almacenaría como un char (36).

Brian Fisher
fuente
55
No puedo ver por qué deberías almacenar -s.
Afshin Mehrabani
2
@AfshinMehrabani Es simple, directo y legible para los humanos. No es necesario, por supuesto, pero si almacenar esos bytes adicionales no hace daño, entonces esta es la mejor solución.
user1717828
2
Almacenar los guiones puede no ser una buena idea porque causará más sobrecarga. Si desea que sea legible para humanos, haga que la aplicación se lea con guiones.
Lucca Ferri
@AfshinMehrabani otra consideración es analizarlo desde la base de datos. La mayoría de las implementaciones esperarán guiones en una guía válida.
Ryan Gates
Puede insertar los guiones al buscar para convertir un char (32) en char (36) fácilmente. use el Insert FN de mySql.
joedotnot
33

Agregando a la respuesta de ThaBadDawg, use estas prácticas funciones (gracias a un colega más sabio mío) para volver de una cadena de 36 longitudes a una matriz de bytes de 16.

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)es en realidad un BINARY(16), elige tu sabor preferido

Para seguir mejor el código, tome el ejemplo dado el GUID ordenado por dígitos a continuación. (Los caracteres ilegales se utilizan con fines ilustrativos: cada uno coloca un carácter único). Las funciones transformarán el orden de los bytes para lograr un orden de bits para una agrupación de índice superior. El guid reordenado se muestra debajo del ejemplo.

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

Guiones eliminados:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW
KCD
fuente
Aquí está la anterior GuidToBinary sin quitar los guiones de la cadena: CREATE FUNCTION GuidToBinary($ carbón GUID (36)) RETURNS binario (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
44
Para los curiosos, estas funciones son superiores a solo UNHEX (REPLACE (UUID (), '-', '')) porque organiza los bits en un orden que funcionará mejor en un índice agrupado.
Slashterix
Esto es muy útil, pero creo que podría mejorarse con una fuente CHARy BINARYequivalencia ( los documentos parecen implicar que hay diferencias importantes y una explicación de por qué el rendimiento del índice agrupado es mejor con los bytes reordenados.
Patrick M
Cuando uso esto, mi guía cambia. Intenté insertarlo usando unhex (replace (string, '-', '')) y la función anterior y cuando los convierto de nuevo usando los mismos métodos, el guid que se selecciona no es el que se insertó. ¿Qué está transformando el guid? Todo lo que he hecho es copiar el código de arriba.
vsdev
@ JonathanOliver ¿Podría compartir el código para la función BinaryToGuid ()?
Arun Avanathan
27

char (36) sería una buena opción. También se puede usar la función UUID () de MySQL que devuelve un formato de texto de 36 caracteres (hexadecimal con guiones) que se puede usar para recuperar dichos ID de la base de datos.

Aprendizaje
fuente
19

"Mejor" depende de lo que esté optimizando.

¿Cuánto le importa el tamaño / rendimiento del almacenamiento frente a la facilidad de desarrollo? Más importante aún: ¿está generando suficientes GUID o buscándolos con la frecuencia suficiente para que sea importante?

Si la respuesta es "no", char(36)es más que suficiente, y hace que el almacenamiento / recuperación de GUID sea muy simple. De lo contrario, binary(16)es razonable, pero tendrá que apoyarse en MySQL y / o en el lenguaje de programación de su elección para convertir de ida y vuelta de la representación de cadena habitual.

candu
fuente
2
Si aloja el software (es decir, una página web, por ejemplo) y no vende / instala en el cliente, siempre puede comenzar con char (36) para un fácil desarrollo en la etapa inicial del software, y mutar a un formato más compacto formato a medida que el sistema crece en uso y comienza a necesitar optimización.
Xavi Montero
1
El mayor inconveniente del char mucho más grande (36) es cuánto espacio ocupará el índice. Si tiene una gran cantidad de registros en la base de datos, está duplicando el tamaño del índice.
bpeikes
8

Binario (16) estaría bien, mejor que el uso de varchar (32).

Onkar Janwa
fuente
7

La rutina GuidToBinary publicada por KCD debe ajustarse para tener en cuenta el diseño de bits de la marca de tiempo en la cadena GUID. Si la cadena representa un UUID de versión 1, como los devueltos por la rutina uuid () mysql, los componentes de tiempo se incrustan en las letras 1-G, excluyendo la 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

Cuando convierte a binario, el mejor orden para la indexación sería: EFG9ABC12345678D + el resto.

No desea cambiar 12345678 a 78563412 porque big endian ya produce el mejor orden de bytes de índice binario. Sin embargo, desea que los bytes más significativos se muevan delante de los bytes inferiores. Por lo tanto, EFG va primero, seguido de los bits intermedios y los bits inferiores. Genere una docena de UUID con uuid () en el transcurso de un minuto y debería ver cómo este orden produce el rango correcto.

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 

Los primeros dos UUID se generaron más cercanos en el tiempo. Solo varían en los últimos 3 mordiscos del primer bloque. Estos son los bits menos significativos de la marca de tiempo, lo que significa que queremos empujarlos hacia la derecha cuando lo convertimos en una matriz de bytes indexable. Como ejemplo de contador, la última ID es la más actual, pero el algoritmo de intercambio del KCD la colocaría antes de la 3ra ID (3e antes de cc, últimos bytes del primer bloque).

El orden correcto para la indexación sería:

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

Consulte este artículo para obtener información de respaldo: http://mysql.rjweb.org/doc.php/uuid

*** tenga en cuenta que no divido el mordisco de la versión de los 12 bits altos de la marca de tiempo. Este es el mordisco de tu ejemplo. Solo lo tiro al frente. Entonces mi secuencia binaria termina siendo DEFG9ABC y así sucesivamente. Esto implica que todos mis UUID indexados comienzan con el mismo mordisco. El artículo hace lo mismo.

bigh_29
fuente
Cuál es el propósito de esto para ahorrar espacio de almacenamiento? o para que ordenarlos sea útil?
MD004
1
@ MD004. Crea un mejor índice de clasificación. El espacio permanece igual.
bigh_29
5

Para aquellos que simplemente se encuentran con esto, ahora hay una alternativa mucho mejor según la investigación de Percona.

Consiste en reorganizar los fragmentos UUID para una indexación óptima, luego convertirlos en binarios para un almacenamiento reducido.

Lee el artículo completo aquí

soñoliento
fuente
Leí ese artículo antes. Me parece muy interesante, pero ¿cómo deberíamos realizar una consulta si queremos filtrar por una ID que sea binaria? Supongo que necesitamos volver a hechizar y luego aplicar los criterios. ¿Es tan exigente? ¿Por qué almacenar binario (16) (seguro que es mejor que varchar (36)) en lugar de bigint de 8 bytes?
Maximus Decimus
2
Hay un artículo actualizado de MariaDB que debería responder a su pregunta mariadb.com/kb/en/mariadb/guiduuid-performance
sleepycal
fwiw, UUIDv4 es completamente aleatorio y no necesita fragmentación.
Mahmoud Al-Qudsi
2

Sugeriría usar las funciones a continuación, ya que las mencionadas por @ bigh_29 transforman mis guías en nuevas (por razones que no entiendo). Además, estos son un poco más rápidos en las pruebas que hice en mis tablas. 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
fuente
-4

si tiene un valor char / varchar formateado como el GUID estándar, simplemente puede almacenarlo como BINARY (16) usando el CAST simple (MyString AS BINARY16), sin todas esas secuencias alucinantes de CONCAT + SUBSTR.

Los campos BINARY (16) se comparan / ordenan / indexan mucho más rápido que las cadenas, y también ocupan dos veces menos espacio en la base de datos

George Hazan
fuente
2
La ejecución de esta consulta muestra que CAST convierte la cadena uuid a bytes ASCII: set @a = uuid (); seleccione @a, hex (cast (@a AS BINARY (16))); Obtengo 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (espacios añadidos para formatear). 0x31 = ascii 1, 0x36 = ascii 6. Incluso obtenemos 0x2D, ​​que es el guión. Esto no es muy diferente a solo almacenar el guid como una cadena, excepto que trunca la cadena en el 16 ° carácter, lo que elimina la parte de la ID que es específica de la máquina.
bigh_29
Sí, esto es simplemente truncamiento. select CAST("hello world, this is as long as uiid" AS BINARY(16));producehello world, thi
MD004