Czy używam varchar (36), czy są na to lepsze sposoby?
Czy używam varchar (36), czy są na to lepsze sposoby?
Odpowiedzi:
Mój DBA zapytał mnie, kiedy zapytałem o najlepszy sposób przechowywania identyfikatorów GUID dla moich obiektów, dlaczego potrzebowałem przechowywać 16 bajtów, skoro mogłem zrobić to samo w 4 bajtach za pomocą liczby całkowitej. Odkąd rzucił mi to wyzwanie, pomyślałem, że teraz jest dobry moment, aby o tym wspomnieć. Biorąc to pod uwagę ...
Możesz przechowywać guid jako binarny CHAR (16), jeśli chcesz maksymalnie optymalnie wykorzystać przestrzeń dyskową.
Przechowałbym to jako char (36).
-
s.
Dodając do odpowiedzi ThaBadDawg, użyj tych przydatnych funkcji (dzięki mojej mądrzejszej koleżance), aby uzyskać od 36 długości łańcucha z powrotem do tablicy bajtów 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)
jest właściwie a BINARY(16)
, wybierz preferowany smak
Aby lepiej postępować zgodnie z kodem, weź przykład z podanym poniżej identyfikatorem GUID uporządkowanym cyframi. (Niedozwolone znaki są używane w celach ilustracyjnych - każde miejsce jest unikalnym znakiem). Funkcje przekształcają kolejność bajtów, aby uzyskać kolejność bitów dla lepszego grupowania indeksów. Ponownie uporządkowany przewodnik jest pokazany poniżej przykładu.
12345678-9ABC-DEFG-HIJK-LMNOPQRSTUVW
78563412-BC9A-FGDE-HIJK-LMNOPQRSTUVW
Usunięte kreski:
123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW
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)));
CHAR
i BINARY
równoważności ( dokumentacja wydaje się sugerować, że istnieją ważne różnice i wyjaśnienie, dlaczego wydajność indeksu klastrowego jest lepsza z uporządkowanymi bajtami.
char (36) byłby dobrym wyborem. Można również użyć funkcji UUID () MySQL, która zwraca 36-znakowy format tekstowy (szesnastkowo z myślnikami), który może być użyty do pobrania takich identyfikatorów z bazy danych.
„Lepsze” zależy od tego, do czego optymalizujesz.
Jak bardzo zależy Ci na rozmiarze / wydajności pamięci masowej w porównaniu z łatwością rozwoju? Co ważniejsze - czy generujesz wystarczającą liczbę identyfikatorów GUID lub pobierasz je wystarczająco często, że ma to znaczenie?
Jeśli odpowiedź brzmi „nie”, char(36)
jest więcej niż wystarczająco dobra i sprawia, że przechowywanie / pobieranie identyfikatorów GUID staje się proste. W przeciwnym razie binary(16)
jest to rozsądne, ale będziesz musiał oprzeć się na MySQL i / lub wybranym języku programowania, aby konwertować w tę iz powrotem ze zwykłej reprezentacji ciągu.
Binarny (16) byłby w porządku, lepszy niż użycie varchar (32).
Procedura GuidToBinary wysłana przez KCD powinna zostać zmodyfikowana, aby uwzględnić układ bitów znacznika czasu w ciągu GUID. Jeśli ciąg reprezentuje identyfikator UUID wersji 1, taki jak zwracany przez procedurę uuid () mysql, to składniki czasu są osadzone w literach 1-G, z wyłączeniem 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
Podczas konwersji do formatu binarnego najlepsza kolejność indeksowania będzie następująca: EFG9ABC12345678D + reszta.
Nie chcesz zamienić 12345678 na 78563412, ponieważ big endian już daje najlepszą kolejność bajtów indeksu binarnego. Jednak chcesz, aby najbardziej znaczące bajty zostały przeniesione przed młodsze bajty. Stąd EFG idzie pierwszy, a następnie środkowe bity i niższe bity. Wygeneruj kilkanaście UUID za pomocą uuid () w ciągu minuty i powinieneś zobaczyć, jak to zamówienie daje prawidłową pozycję.
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
Pierwsze dwa identyfikatory UUID zostały wygenerowane najbliżej w czasie. Różnią się one tylko w ostatnich 3 skubaniach pierwszego bloku. Są to najmniej znaczące bity znacznika czasu, co oznacza, że chcemy przesunąć je w prawo, gdy konwertujemy to na indeksowalną tablicę bajtów. Jako przykład licznika, ostatni identyfikator jest najbardziej aktualny, ale algorytm zamiany KCD umieściłby go przed trzecim identyfikatorem (3e przed dc, ostatnie bajty z pierwszego bloku).
Prawidłowa kolejność indeksowania to:
1e497556eec5eb6...
1e497556eec5f10...
1e497556eec8ddc...
1e497556eee30d0...
1e497556efda038...
1e497556f9641bf...
1e49755758c3e3e...
Dodatkowe informacje można znaleźć w tym artykule: http://mysql.rjweb.org/doc.php/uuid
*** Zwróć uwagę, że nie dzielę skubania wersji od wysokich 12 bitów znacznika czasu. To jest skubać D z twojego przykładu. Po prostu rzucam to przed siebie. Więc moja sekwencja binarna kończy się na DEFG9ABC i tak dalej. Oznacza to, że wszystkie moje indeksowane identyfikatory UUID zaczynają się od tego samego skubacza. Artykuł robi to samo.
Dla tych, którzy się na to natkną, jest teraz znacznie lepsza alternatywa, jak wynika z badań przeprowadzonych przez Perconę.
Obejmuje reorganizację fragmentów UUID w celu optymalnego indeksowania, a następnie konwersję na binarną w celu zmniejszenia ilości pamięci.
Przeczytaj cały artykuł tutaj
Sugerowałbym użycie poniższych funkcji, ponieważ te wymienione przez @ bigh_29 przekształcają moje guidery w nowe (z powodów, których nie rozumiem). Są też trochę szybsze w testach, które przeprowadziłem na moich stołach. 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 ;
jeśli masz wartość char / varchar sformatowaną jako standardowy identyfikator GUID, możesz po prostu zapisać ją jako BINARY (16) za pomocą prostego CAST (MyString AS BINARY16), bez tych wszystkich zadziwiających sekwencji CONCAT + SUBSTR.
Pola BINARY (16) są porównywane / sortowane / indeksowane znacznie szybciej niż łańcuchy, a także zajmują dwa razy mniej miejsca w bazie danych
select CAST("hello world, this is as long as uiid" AS BINARY(16));
produkujehello world, thi