Wie soll ich GUID in MySQL-Tabellen speichern?


146

Benutze ich varchar (36) oder gibt es bessere Möglichkeiten, dies zu tun?


1
"thaBadDawg" bietet eine gute Antwort. Es gibt einen parallelen Thread zum Stapelüberlauf, der das Thema behandelt. Ich habe einige Kommentare zu diesen Threads hinzugefügt, die diesen Link zu Ressourcen detaillierter beantworten. Hier ist der Fragenlink : stackoverflow.com/questions/547118/storing-mysql-guid-uuids - Ich gehe davon aus, dass dieses Thema häufiger wird, wenn Leute über AWS und Aurora nachdenken .
Zack Jannsen

Antworten:


104

Mein DBA fragte mich, als ich nach dem besten Weg zum Speichern von GUIDs für meine Objekte fragte, warum ich 16 Bytes speichern musste, wenn ich dasselbe in 4 Bytes mit einer Ganzzahl tun konnte. Da er mir diese Herausforderung gestellt hat, dachte ich, jetzt sei ein guter Zeitpunkt, sie zu erwähnen. Davon abgesehen ...

Sie können eine Guid als CHAR (16) -Binärdatei speichern, wenn Sie den Speicherplatz optimal nutzen möchten.


176
Denn mit 16 Bytes können Sie Dinge in verschiedenen Datenbanken, auf verschiedenen Computern und zu verschiedenen Zeiten generieren und trotzdem die Daten nahtlos zusammenführen :)
Billy ONeal

4
brauche eine Antwort, was ist wirklich eine char 16 Binärdatei? nicht char? nicht binär? Ich sehe diesen Typ weder in einem der MySQL-GUI-Tools noch in einer Dokumentation auf der MySQL-Site. @ BillyONeal
Nawfal

3
@nawfal: Char ist der Datentyp. BINARY ist der Typbezeichner für den Typ. Der einzige Effekt besteht darin, die Sortierung von MySQL zu ändern. Weitere Informationen finden Sie unter dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html . Natürlich können Sie einen BINARY-Typ auch direkt verwenden, wenn Sie dies mit Ihrem Datenbankbearbeitungswerkzeug tun können. (Ältere Tools kennen den Binärdatentyp nicht, kennen aber das
Binärspaltenflag

2
Ein CHAR- und ein BINARY-Feld sind im Wesentlichen gleich. Wenn Sie es auf die grundlegendste Ebene bringen möchten, ist ein CHAR ein Binärfeld, das einen Wert von 0 bis 255 erwartet, um diesen Wert mit einem Wert darzustellen, der aus einer Nachschlagetabelle zugeordnet ist (in den meisten Fällen jetzt UTF8). Ein BINARY-Feld erwartet dieselbe Art von Wert, ohne die Absicht, diese Daten aus einer Nachschlagetabelle darzustellen. Ich habe CHAR (16) in den 4.x Tagen verwendet, weil MySQL damals nicht so gut war wie heute.
thaBadDawg

15
Es gibt mehrere gute Gründe, warum eine GUID weitaus besser ist als eine automatische Erhöhung. Jeff Atwood listet diese auf . Für mich ist der beste Vorteil bei der Verwendung einer GUID, dass meine App keinen Datenbank-Roundtrip benötigt, um den Schlüssel einer Entität zu kennen: Ich könnte ihn programmgesteuert füllen, was ich nicht tun könnte, wenn ich ein Feld für die automatische Inkrementierung verwenden würde. Dies ersparte mir einige Kopfschmerzen: Mit GUID kann ich die Entität auf die gleiche Weise verwalten, unabhängig davon, ob die Entität bereits beibehalten wurde oder eine brandneue ist.
Arialdo Martini

48

Ich würde es als Zeichen speichern (36).


5
Ich kann nicht verstehen, warum Sie -s speichern sollten .
Afshin Mehrabani

2
@AfshinMehrabani Es ist einfach, unkompliziert und für Menschen lesbar. Es ist natürlich nicht notwendig, aber wenn das Speichern dieser zusätzlichen Bytes nicht schadet, ist dies die beste Lösung.
user1717828

2
Das Speichern der Bindestriche ist möglicherweise keine gute Idee, da dadurch mehr Overhead verursacht wird. Wenn Sie es für Menschen lesbar machen möchten, lassen Sie die Anwendung mit den Bindestrichen lesen.
Lucca Ferri

@AfshinMehrabani Eine weitere Überlegung ist das Parsen aus der Datenbank. Die meisten Implementierungen erwarten Bindestriche in einer gültigen Guid.
Ryan Gates

Sie können beim Abrufen die Bindestriche einfügen, um ein Zeichen (32) einfach in ein Zeichen (36) umzuwandeln. Verwenden Sie die Insert FN von mySql.
Joedotnot

33

Verwenden Sie diese praktischen Funktionen (dank eines klügeren Kollegen von mir), um die Antwort von ThaBadDawg zu ergänzen und von einer Zeichenfolge mit einer Länge von 36 zu einem Byte-Array von 16 zurückzukehren.

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)ist eigentlich ein BINARY(16), wählen Sie Ihren bevorzugten Geschmack

Um dem Code besser zu folgen, nehmen Sie das folgende Beispiel mit der nach Ziffern geordneten GUID. (Unzulässige Zeichen werden zur Veranschaulichung verwendet - jede Stelle enthält ein eindeutiges Zeichen.) Die Funktionen transformieren die Bytereihenfolge, um eine Bitreihenfolge für eine überlegene Indexclusterung zu erreichen. Die neu geordnete Guid wird unter dem Beispiel gezeigt.

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

Striche entfernt:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW

Hier ist die obige GuidToBinary, ohne die Bindestriche aus der Zeichenfolge zu entfernen: CREATE FUNCTION GuidToBinary($ guid char (36)) RETURNS binär (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
Für Neugierige sind diese Funktionen nur UNHEX (REPLACE (UUID (), '-', '')) überlegen, da sie die Bits in einer Reihenfolge anordnen, die in einem Clustered-Index eine bessere Leistung erbringt.
Slashterix

Dies ist sehr hilfreich, aber ich denke, es könnte mit einer Quelle für CHARund einer BINARYÄquivalenz verbessert werden ( die Dokumente scheinen zu implizieren, dass es wichtige Unterschiede gibt und eine Erklärung dafür, warum die Leistung von Clustered-Indizes bei neu geordneten Bytes besser ist.
Patrick M

Wenn ich das benutze, wird meine Guid geändert. Ich habe versucht, es sowohl mit unhex (replace (string, '-', '')) als auch mit der obigen Funktion einzufügen. Wenn ich sie mit denselben Methoden zurückkonvertiere, ist die ausgewählte Guid nicht diejenige, die eingefügt wurde. Was verändert die Guid? Ich habe nur den Code von oben kopiert.
vsdev

@JonathanOliver Könnten Sie bitte den Code für die Funktion BinaryToGuid () freigeben?
Arun Avanathan

27

char (36) wäre eine gute Wahl. Es kann auch die UUID () - Funktion von MySQL verwendet werden, die ein 36-stelliges Textformat (hexadezimal mit Bindestrichen) zurückgibt, das zum Abrufen solcher IDs aus der Datenbank verwendet werden kann.


19

"Besser" hängt davon ab, wofür Sie optimieren.

Wie wichtig ist Ihnen die Speichergröße / -leistung im Vergleich zur einfachen Entwicklung? Noch wichtiger: Generieren Sie genügend GUIDs oder rufen Sie sie häufig genug ab, damit es darauf ankommt?

Wenn die Antwort "Nein" char(36)lautet , ist dies mehr als gut genug und das Speichern / Abrufen von GUIDs ist kinderleicht. Andernfalls binary(16)ist dies sinnvoll, aber Sie müssen sich auf MySQL und / oder die Programmiersprache Ihrer Wahl stützen, um von der üblichen Zeichenfolgendarstellung hin und her zu konvertieren.


2
Wenn Sie die Software hosten (z. B. eine Webseite) und nicht im Client verkaufen / installieren, können Sie jederzeit mit char (36) beginnen, um die Entwicklung in der frühen Phase der Software zu vereinfachen, und zu einer kompakteren Version mutieren Format, wenn das System immer mehr genutzt wird und optimiert werden muss.
Xavi Montero

1
Der größte Nachteil des viel größeren Zeichens (36) ist, wie viel Platz der Index einnimmt. Wenn Sie eine große Anzahl von Datensätzen in der Datenbank haben, verdoppeln Sie die Größe des Index.
Bpeikes

8

Binär (16) wäre in Ordnung, besser als die Verwendung von Varchar (32).


7

Die von KCD veröffentlichte GuidToBinary-Routine sollte angepasst werden, um das Bitlayout des Zeitstempels in der GUID-Zeichenfolge zu berücksichtigen. Wenn die Zeichenfolge eine UUID der Version 1 darstellt, wie sie von der MySQL-Routine uuid () zurückgegeben wird, werden die Zeitkomponenten in die Buchstaben 1-G eingebettet, mit Ausnahme des 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

Wenn Sie in Binär konvertieren, lautet die beste Reihenfolge für die Indizierung: EFG9ABC12345678D + der Rest.

Sie möchten 12345678 nicht gegen 78563412 tauschen, da Big Endian bereits die beste Bytereihenfolge für binäre Indizes liefert. Sie möchten jedoch, dass die höchstwertigen Bytes vor die unteren Bytes verschoben werden. Daher geht EFG zuerst, gefolgt von den mittleren und unteren Bits. Generieren Sie mit uuid () im Laufe einer Minute etwa ein Dutzend UUIDs, und Sie sollten sehen, wie diese Reihenfolge den richtigen Rang ergibt.

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 

Die ersten beiden UUIDs wurden zeitnah generiert. Sie variieren nur in den letzten 3 Knabbereien des ersten Blocks. Dies sind die niedrigstwertigen Bits des Zeitstempels, was bedeutet, dass wir sie nach rechts verschieben möchten, wenn wir dies in ein indexierbares Byte-Array konvertieren. Als Gegenbeispiel ist die letzte ID die aktuellste, aber der Austauschalgorithmus der KCD würde sie vor die dritte ID setzen (3e vor DC, letzte Bytes aus dem ersten Block).

Die richtige Reihenfolge für die Indizierung wäre:

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

Weitere Informationen finden Sie in diesem Artikel: http://mysql.rjweb.org/doc.php/uuid

*** Beachten Sie, dass ich das Versions-Nibble nicht von den hohen 12 Bits des Zeitstempels trenne. Dies ist das D-Knabbern aus Ihrem Beispiel. Ich werfe es einfach vor. Meine binäre Sequenz lautet also DEFG9ABC und so weiter. Dies bedeutet, dass alle meine indizierten UUIDs mit demselben Nibble beginnen. Der Artikel macht das Gleiche.


ist dies der Zweck, um Speicherplatz zu sparen? oder um das Sortieren nützlich zu machen?
MD004

1
@ MD004. Es wird ein besserer Sortierindex erstellt. Der Raum bleibt gleich.
bigh_29

5

Für diejenigen, die nur darüber stolpern, gibt es jetzt eine viel bessere Alternative als nach Untersuchungen von Percona.

Es besteht darin, die UUID-Blöcke für eine optimale Indizierung neu zu organisieren und sie dann zur Reduzierung des Speichers in eine Binärdatei umzuwandeln.

Lesen Sie den ganzen Artikel hier


Ich habe diesen Artikel schon einmal gelesen. Ich finde es sehr interessant, aber wie sollen wir dann eine Abfrage durchführen, wenn wir nach einer binären ID filtern möchten? Ich denke, wir müssen erneut verhexen und dann die Kriterien anwenden. Ist es so anspruchsvoll? Warum Binär (16) speichern (sicher ist es besser als Varchar (36)) anstelle von Bigint von 8 Bytes?
Maximus Decimus

2
Es gibt einen aktualisierten Artikel von MariaDB, der Ihre Frage beantworten sollte mariadb.com/kb/en/mariadb/guiduuid-performance
sleepycal

fwiw, UUIDv4 ist völlig zufällig und benötigt kein Chunking.
Mahmoud Al-Qudsi

2

Ich würde vorschlagen, die folgenden Funktionen zu verwenden, da die von @ bigh_29 erwähnten meine Guids in neue umwandeln (aus Gründen, die ich nicht verstehe). Außerdem sind diese in den Tests, die ich an meinen Tischen durchgeführt habe, etwas schneller. 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

Wenn Sie einen char / varchar-Wert als Standard-GUID formatiert haben, können Sie ihn einfach mit dem einfachen CAST (MyString AS BINARY16) als BINARY (16) speichern, ohne all diese umwerfenden Sequenzen von CONCAT + SUBSTR.

BINARY (16) -Felder werden viel schneller als Strings verglichen / sortiert / indiziert und benötigen außerdem zweimal weniger Speicherplatz in der Datenbank


2
Das Ausführen dieser Abfrage zeigt, dass CAST die UUID-Zeichenfolge in ASCII-Bytes konvertiert: set @a = uuid (); wähle @a, hex (cast (@a AS BINARY (16))); Ich erhalte 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (Leerzeichen zur Formatierung hinzugefügt). 0x31 = ascii 1, 0x36 = ascii 6. Wir erhalten sogar 0x2D, ​​den Bindestrich. Dies unterscheidet sich nicht wesentlich vom Speichern der Guid als Zeichenfolge, mit der Ausnahme, dass Sie die Zeichenfolge beim 16. Zeichen abschneiden, wodurch der maschinenspezifische Teil der ID abgeschnitten wird.
bigh_29

Ja, das ist einfach Kürzung. select CAST("hello world, this is as long as uiid" AS BINARY(16));produzierthello world, thi
MD004
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.