Comment dois-je stocker le GUID dans les tables MySQL?


146

Dois-je utiliser varchar (36) ou y a-t-il de meilleures façons de le faire?


1
"thaBadDawg" offre une bonne réponse. Il existe un thread parallèle sur Stack Overflow qui traite du sujet. J'ai ajouté quelques commentaires sur les fils de discussion qui renvoient aux ressources avec plus de détails. Voici le lien de la question: stackoverflow.com/questions/547118/storing-mysql-guid-uuids - Je m'attends à ce que ce sujet devienne plus courant lorsque les gens commenceront à envisager AWS et Aurora.
Zack Jannsen le

Réponses:


104

Mon DBA m'a demandé quand j'ai demandé la meilleure façon de stocker les GUID pour mes objets pourquoi je devais stocker 16 octets alors que je pouvais faire la même chose en 4 octets avec un entier. Depuis qu'il m'a lancé ce défi, j'ai pensé que le moment était venu de le mentionner. Cela étant dit...

Vous pouvez stocker un guid sous forme de binaire CHAR (16) si vous souhaitez utiliser au mieux l'espace de stockage.


176
Parce qu'avec 16 octets, vous pouvez générer des choses dans différentes bases de données, sur différentes machines, à des moments différents, et toujours fusionner les données ensemble de manière transparente :)
Billy ONeal

4
besoin de réponse, qu'est-ce qu'un binaire char 16? pas de char? pas binaire? Je ne vois ce type dans aucun des outils de mysql gui, ni dans aucune documentation sur le site mysql. @BillyONeal
nawfal

3
@nawfal: Char est le type de données. BINARY est le spécificateur de type par rapport au type. Le seul effet qu'il a est de modifier la façon dont MySQL effectue le classement. Voir dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html pour plus de détails. Bien sûr, vous pouvez simplement utiliser un type BINARY directement si votre outil d'édition de base de données vous permet de le faire. (Les outils plus anciens ne connaissent pas le type de données binaires mais connaissent l'indicateur de colonne binaire)
Billy ONeal

2
un champ CHAR et un champ BINARY sont essentiellement les mêmes. Si vous voulez l'amener au niveau le plus basique, un CHAR est un champ binaire qui attend une valeur de 0 à 255 dans le but de représenter ladite valeur avec une valeur mappée à partir d'une table de consultation (dans la plupart des cas maintenant, UTF8). Un champ BINARY attend le même type de valeur sans aucune intention de représenter lesdites données à partir d'une table de recherche. J'ai utilisé CHAR (16) à l'époque 4.x parce qu'à l'époque MySQL n'était pas aussi bon qu'il l'est maintenant.
thaBadDawg

15
Il y a plusieurs bonnes raisons pour lesquelles un GUID est bien meilleur qu'un auto-incrémentation. Jeff Atwood énumère ces un . Pour moi, le meilleur avantage de l'utilisation d'un GUID est que mon application n'aura pas besoin d'un aller-retour de base de données pour connaître la clé d'une entité: je pourrais la remplir par programme, ce que je ne pourrais pas faire si j'utilisais un champ à incrémentation automatique. Cela m'a évité plusieurs maux de tête: avec GUID, je peux gérer l'entité de la même manière, quelle que soit l'entité qui a déjà été persistée ou qu'elle est toute nouvelle.
Arialdo Martini

48

Je le stockerais sous forme de caractère (36).


5
Je ne vois pas pourquoi vous devriez stocker -s.
Afshin Mehrabani

2
@AfshinMehrabani C'est simple, direct, lisible par l'homme. Ce n'est pas nécessaire, bien sûr, mais si stocker ces octets supplémentaires ne fait pas de mal, c'est la meilleure solution.
user1717828

2
Le stockage des tirets n'est peut-être pas une bonne idée car cela entraînera plus de frais généraux. Si vous voulez le rendre lisible par l'homme, faites en sorte que l'application soit lue avec les tirets.
Lucca Ferri

@AfshinMehrabani une autre considération est de l'analyser à partir de la base de données. La plupart des implémentations s'attendront à des tirets dans un guid valide.
Ryan Gates

Vous pouvez insérer les traits d'union lors de la récupération pour convertir facilement un char (32) en char (36). utilisez l'insertion FN de mySql.
joedotnot

33

Pour ajouter à la réponse de ThaBadDawg, utilisez ces fonctions pratiques (grâce à un de mes collègues plus sages) pour passer d'une chaîne de 36 longueurs à un tableau d'octets 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)est en fait un BINARY(16), choisissez votre saveur préférée

Pour mieux suivre le code, prenez l'exemple donné le GUID par ordre de chiffres ci-dessous. (Les caractères illégaux sont utilisés à des fins d'illustration - chaque place un caractère unique.) Les fonctions transformeront l'ordre des octets pour obtenir un ordre de bits pour une classification d'index supérieure. Le guid réorganisé est affiché sous l'exemple.

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

Tirets supprimés:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW

Voici le GuidToBinary ci-dessus sans enlever les tirets de la chaîne: CREATE FUNCTION GuidToBinary($ guid char (36)) RETURNS binary (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
Pour les curieux, ces fonctions sont supérieures à UNHEX (REPLACE (UUID (), '-', '')) car elles arrangent les bits dans un ordre qui fonctionnera mieux dans un index clusterisé.
Slashterix

C'est très utile, mais je pense que cela pourrait être amélioré avec une source pour CHARet une BINARYéquivalence ( la documentation semble impliquer qu'il existe des différences importantes et une explication des raisons pour lesquelles les performances des index clusterisés sont meilleures avec des octets réorganisés.
Patrick M

Lorsque j'utilise cela, mon guide est modifié. J'ai essayé de l'insérer en utilisant à la fois unhex (replace (string, '-', '')) et la fonction ci-dessus et lorsque je les reconvertis en utilisant les mêmes méthodes, le guid qui est sélectionné n'est pas celui qui a été inséré. Qu'est-ce qui transforme le guid? Tout ce que j'ai fait est de copier le code ci-dessus.
vsdev

@JonathanOliver Pourriez-vous s'il vous plaît partager le code de la fonction BinaryToGuid ()?
Arun Avanathan

27

char (36) serait un bon choix. La fonction UUID () de MySQL peut également être utilisée, ce qui renvoie un format de texte à 36 caractères (hex avec tirets) qui peut être utilisé pour récupérer ces ID à partir de la base de données.


19

«Mieux» dépend de ce pour quoi vous optimisez.

Dans quelle mesure vous souciez-vous de la taille / des performances du stockage par rapport à la facilité de développement? Plus important encore, générez-vous suffisamment de GUID ou les récupérez-vous assez fréquemment pour que cela compte?

Si la réponse est "non", char(36)c'est plus que suffisant, et cela rend le stockage / récupération des GUID extrêmement simple. Sinon, binary(16)c'est raisonnable, mais vous devrez vous appuyer sur MySQL et / ou votre langage de programmation de choix pour convertir dans les deux sens à partir de la représentation sous forme de chaîne habituelle.


2
Si vous hébergez le logiciel (c'est-à-dire une page Web par exemple) et que vous ne vendez / installez pas dans le client, vous pouvez toujours commencer par char (36) pour un développement facile au tout début du logiciel, et passer à un format plus compact format au fur et à mesure que le système se développe et nécessite une optimisation.
Xavi Montero

1
Le plus gros inconvénient du caractère beaucoup plus grand (36) est l'espace que prendra l'index. Si vous avez un grand nombre d'enregistrements dans la base de données, vous doublez la taille de l'index.
vélos


7

La routine GuidToBinary publiée par KCD doit être modifiée pour tenir compte de la disposition de bits de l'horodatage dans la chaîne GUID. Si la chaîne représente un UUID version 1, comme ceux retournés par la routine mysql uuid (), alors les composants de temps sont incorporés dans les lettres 1-G, à l'exclusion du 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

Lorsque vous convertissez en binaire, le meilleur ordre d'indexation serait: EFG9ABC12345678D + le reste.

Vous ne voulez pas échanger 12345678 contre 78563412 car big endian produit déjà le meilleur ordre d'octets d'index binaire. Cependant, vous voulez que les octets les plus significatifs soient déplacés devant les octets inférieurs. Par conséquent, EFG passe en premier, suivi des bits du milieu et des bits inférieurs. Générez une douzaine d'UUID avec uuid () en une minute et vous devriez voir comment cet ordre donne le rang correct.

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 

Les deux premiers UUID ont été générés au plus près dans le temps. Ils ne varient que dans les 3 derniers grignotages du premier bloc. Ce sont les bits les moins significatifs de l'horodatage, ce qui signifie que nous voulons les pousser vers la droite lorsque nous le convertissons en un tableau d'octets indexables. À titre d'exemple de compteur, le dernier ID est le plus courant, mais l'algorithme de permutation du KCD le placerait avant le 3e ID (3e avant dc, derniers octets du premier bloc).

L'ordre correct pour l'indexation serait:

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

Consultez cet article pour obtenir des informations complémentaires : http://mysql.rjweb.org/doc.php/uuid

*** notez que je ne sépare pas le grignotage de la version des 12 bits supérieurs de l'horodatage. C'est le grignotage D de votre exemple. Je le jette juste devant. Donc ma séquence binaire finit par être DEFG9ABC et ainsi de suite. Cela implique que tous mes UUID indexés commencent par le même quartet. L'article fait la même chose.


est le but de cela pour économiser de l'espace de stockage? ou pour rendre leur tri utile?
MD004

1
@ MD004. Cela crée un meilleur index de tri. L'espace reste le même.
bigh_29

5

Pour ceux qui ne font que trébucher sur cela, il existe maintenant une bien meilleure alternative selon les recherches de Percona.

Il consiste à réorganiser les blocs UUID pour une indexation optimale, puis à les convertir en binaire pour un stockage réduit.

Lisez l'article complet ici


J'ai lu cet article avant. Je trouve cela très intéressant mais alors comment faire une requête si l'on veut filtrer par un identifiant qui est binaire? Je suppose que nous devons à nouveau jeter un sort puis appliquer les critères. Est-ce si exigeant? Pourquoi stocker le binaire (16) (sûr que c'est mieux que varchar (36)) au lieu de bigint de 8 octets?
Maximus Decimus

2
Il y a un article mis à jour de MariaDB qui devrait répondre à votre question mariadb.com/kb/en/mariadb/guiduuid-performance
sleepycal

fwiw, UUIDv4 est complètement aléatoire et ne nécessite aucun découpage.
Mahmoud Al-Qudsi

2

Je suggérerais d'utiliser les fonctions ci-dessous puisque celles mentionnées par @ bigh_29 transforment mes guides en de nouveaux (pour des raisons que je ne comprends pas). De plus, ceux-ci sont un peu plus rapides dans les tests que j'ai effectués sur mes tables. 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 ;

-4

si vous avez une valeur char / varchar formatée comme GUID standard, vous pouvez simplement la stocker en tant que BINARY (16) en utilisant le simple CAST (MyString AS BINARY16), sans toutes ces séquences époustouflantes de CONCAT + SUBSTR.

Les champs BINARY (16) sont comparés / triés / indexés beaucoup plus rapidement que les chaînes, et prennent également deux fois moins d'espace dans la base de données


2
L'exécution de cette requête montre que CAST convertit la chaîne uuid en octets ASCII: set @a = uuid (); sélectionnez @a, hex (cast (@a AS BINARY (16))); J'obtiens 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (espaces ajoutés pour le formatage). 0x31 = ascii 1, 0x36 = ascii 6. Nous obtenons même 0x2D, ​​qui est le trait d'union. Ce n'est pas très différent du simple stockage du guid sous forme de chaîne, sauf que vous tronquez la chaîne au 16ème caractère, ce qui supprime la partie de l'ID qui est spécifique à la machine.
bigh_29

Oui, c'est simplement une troncature. select CAST("hello world, this is as long as uiid" AS BINARY(16));produithello world, thi
MD004
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.