GUID'i MySQL tablolarında nasıl saklamalıyım?


146

Varchar (36) kullanıyor muyum yoksa bunu yapmanın daha iyi bir yolu var mı?


1
"thaBadDawg" iyi bir cevap sunuyor. Stack Overflow'da konuyu tartışan paralel bir iş parçacığı vardır. Bu konulara kaynakların bağlantısını daha ayrıntılı olarak cevaplayan bazı yorumlar ekledim. Soru bağlantısı: stackoverflow.com/questions/547118/storing-mysql-guid-uuids - İnsanlar AWS ve Aurora'yı düşünmeye başladığında bu konunun daha yaygın olmasını bekliyorum.
Zack Jannsen

Yanıtlar:


104

DBA'm, nesnelerim için GUID'leri saklamanın en iyi yolunu sorduğumda, aynı şeyi bir tamsayı ile 4 baytta yapabildiğimde neden 16 bayt depolamaya ihtiyacım olduğunu sordu. Bana bu meydan okumayı koyduğundan beri, bundan bahsetmek için iyi bir zaman olduğunu düşündüm. Söyleniyor ki...

Depolama alanını en iyi şekilde kullanmak istiyorsanız, bir kılavuzu CHAR (16) ikili dosyası olarak saklayabilirsiniz.


176
Çünkü 16 bayt ile, farklı veritabanlarında, farklı makinelerde, farklı zamanlarda bir şeyler üretebilir ve yine de verileri sorunsuz bir şekilde birleştirebilirsiniz :)
Billy ONeal

4
cevap lazım, gerçekten bir char 16 ikili nedir? char değil mi? ikili değil mi? Ben mysql gui araçların hiçbirinde, ne de mysql sitesinde herhangi bir belge görmüyorum. @BillyONeal
nawfal

3
@nawfal: Char veri tipidir. BINARY, türe karşı tür belirtecidir. Tek etkisi MySQL'in harmanlama şeklini değiştirmektir. Daha fazla bilgi için dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html adresine bakın. Tabii ki veritabanı düzenleme aracınız bunu yapmanıza izin veriyorsa doğrudan bir BINARY türü kullanabilirsiniz. (Eski araçlar ikili veri türünü bilmiyor, ancak ikili sütun bayrağını biliyor)
Billy ONeal

2
bir CHAR ve bir BINARY alanı esasen aynıdır. En temel düzeylere götürmek istiyorsanız, CHAR, söz konusu değeri bir arama tablosundan eşlenen bir değerle (çoğu durumda şimdi UTF8) temsil etmek amacıyla 0 ila 255 değer bekleyen bir ikili alandır. İKİLİ alan, bir arama tablosundan adı geçen verileri temsil etme niyeti olmadan aynı tür bir değer bekler. CHS'yi (16) 4.x günlerde kullandım çünkü o zamanlar MySQL şu anki kadar iyi değildi.
thaBadDawg

15
Bir GUID'nin otomatik bir artıştan çok daha iyi olmasının birkaç iyi nedeni vardır. Jeff Atwood listeler bu bir . Bana göre, bir GUID kullanmanın en iyi avantajı, uygulamamın bir varlığın anahtarını bilmek için bir veritabanı gidiş dönüşüne ihtiyaç duymamasıdır: Programlı olarak doldurabilirim. Bu beni birkaç baş ağrısından kurtardı: GUID ile, varlığın zaten devam ettiğinden veya yepyeni bir şeyden bağımsız olarak varlığı aynı şekilde yönetebilirim.
Arialdo Martini

48

Onu bir karakter olarak saklardım (36).


5
Neden saklamanız gerektiğini anlayamıyorum -.
Afshin Mehrabani

2
@AfshinMehrabani Basit, anlaşılır, insan tarafından okunabilir. Tabii ki gerekli değildir, ancak bu ekstra baytları saklamak zarar vermezse, bu en iyi çözümdür.
user1717828

2
Kısa çizgileri saklamak iyi bir fikir olmayabilir, çünkü daha fazla ek yüke neden olacaktır. İnsan tarafından okunabilir olmasını istiyorsanız, uygulamayı kısa çizgilerle okuyun.
Lucca Ferri

@AfshinMehrabani bir başka husus veritabanından ayrıştırmaktır. Çoğu uygulama geçerli bir kılavuzda tire işaretleri bekler.
Ryan Gates

Karakterleri (32) karakterlere (36) kolayca dönüştürmek için getirirken kısa çizgiler ekleyebilirsiniz. mySql Ekle FN'sini kullanın.
joedotnot

33

ThaBadDawg'ın cevabına ek olarak, 36 uzunluklu dizeden 16 baytlık diziye geri dönmek için bu kullanışlı işlevleri (daha akıllı bir meslektaşım sayesinde) kullanın.

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)aslında bir BINARY(16), tercih ettiğiniz lezzeti seçin

Kodu daha iyi takip etmek için, aşağıdaki basamaklı GUID verilen örneği alın. (Geçersiz karakterler açıklama amacıyla kullanılır - her biri benzersiz bir karakter kullanır.) İşlevler, üstün dizin kümelemesi için bir bit sırası elde etmek üzere bayt sırasını dönüştürür. Yeniden sıralanan kılavuz örneğin altında gösterilmiştir.

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

Kısa çizgiler kaldırıldı:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW

İşte dizeden tire işaretleri kaldırmadan yukarıdaki GuidToBinary: CREATE FUNCTION GuidToBinary($ guid char (36)) RETURNS ikili (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
Meraklı olanlar için, bu işlevler sadece UNHEX'ten (REPLACE (UUID (), '-', '')) üstündür, çünkü bitleri kümelenmiş bir dizinde daha iyi performans gösterecek şekilde düzenler.
Slashterix

Bu çok yararlı, ancak bunun bir kaynak CHARve BINARYeşdeğerlik ile geliştirilebileceğini hissediyorum ( dokümanlar önemli farklılıklar olduğunu ve kümelenmiş dizin performansının yeniden sıralanan baytlarla neden daha iyi olduğunu açıklıyor gibi görünüyor.
Patrick M

Bunu kullandığımda rehberim değişti. Her iki unhex (replace (string, '-', '')) ve yukarıdaki işlevi kullanarak eklemeyi denedim ve aynı yöntemleri kullanarak onları geri dönüştürdüğümde seçilen kılavuz eklenen bir değil. Kılavuzu değiştiren nedir? Tüm yaptığım kod yukarıdan kopyalandı.
vsdev

@JonathanOliver BinaryToGuid () fonksiyonunun kodunu paylaşabilir misiniz?
Arun Avanathan

27

char (36) iyi bir seçim olacaktır. Ayrıca MySQL'in UUID () işlevi, db'den bu tür kimlikleri almak için kullanılabilen 36 karakterlik bir metin biçimini (tire ile onaltılık) döndürür.


19

"Daha iyi" ne için optimizasyon yaptığınıza bağlıdır.

Depolama boyutuna / performansına kıyasla geliştirme kolaylığına ne kadar önem veriyorsunuz? Daha da önemlisi - önemli olan yeterince GUID oluşturuyor veya yeterince sık alıyor musunuz?

Cevap "hayır" ise char(36), yeterince iyi ise ve GUID'leri depolamayı / getirmeyi çok basit hale getirir. Aksi takdirde, binary(16)mantıklıdır, ancak normal dize gösteriminden ileri geri dönüş yapmak için MySQL ve / veya seçtiğiniz programlama diline yaslanmanız gerekir.


2
Yazılımı barındırıyorsanız (örneğin bir web sayfası) ve istemcide satmaz / kurmazsanız, yazılımın erken aşamasında kolay geliştirme için her zaman char (36) ile başlayabilir ve daha kompakt bir hale getirebilirsiniz. sistem kullanımda büyüdükçe ve optimizasyona ihtiyaç duyulduğunda biçimlendirin.
Xavi Montero

1
Çok daha büyük karakterlerin en büyük aşağı tarafı (36) endeksin ne kadar yer alacağıdır. Veritabanında çok sayıda kayıt varsa, dizin boyutunu iki katına çıkarırsınız.
bpeikes


7

KCD tarafından yayınlanan GuidToBinary yordamı, GUID dizesindeki zaman damgasının bit düzenini dikkate almak için ayarlanmalıdır. Dize, uuid () mysql yordamı tarafından döndürülenler gibi bir sürüm 1 UUID'yi temsil ediyorsa, zaman bileşenleri D hariç 1-G harflerine gömülür.

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

İkili dosyaya dönüştürdüğünüzde, dizine ekleme için en iyi sipariş şu şekildedir: EFG9ABC12345678D + geri kalanı.

12345678 ile 78563412 arasında geçiş yapmak istemezsiniz, çünkü büyük endian zaten en iyi ikili dizin bayt sırasını verir. Ancak, en önemli baytların alt baytların önüne taşınmasını istiyorsunuz. Bu nedenle EFG önce gelir, ardından orta bitler ve alt bitler gelir. Bir dakika içinde uuid () ile bir düzine kadar UUID oluşturun ve bu siparişin nasıl doğru sıralamayı verdiğini görmelisiniz.

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 

İlk iki UUID en yakın zamanda üretildi. Sadece ilk bloğun son 3 kemerinde değişiklik gösterirler. Bunlar zaman damgasının en az önemli bitleridir, yani bunu dizine eklenebilir bir bayt dizisine dönüştürdüğümüzde onları sağa doğru itmek istiyoruz. Bir karşı örnek olarak, son kimlik en güncel olanıdır, ancak KCD'nin takas algoritması bunu 3. ID'den önce yerleştirir (dc'den 3e, ilk bloktan son bayt).

Dizinleme için doğru sıra şu şekildedir:

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

Destekleyici bilgiler için bu makaleye bakın: http://mysql.rjweb.org/doc.php/uuid

*** Ben zaman damgası yüksek 12 bit sürümü nibble bölünmedi unutmayın. Bu, örneğinizdeki D kemeri. Sadece öne atıyorum. Böylece ikili dizilim DEFG9ABC ve benzeri oluyor. Bu, tüm dizinlenmiş UUID'lerimin aynı uç ile başladığını gösterir. Makale de aynı şeyi yapıyor.


depolama alanı kazanmak için amacı nedir? ya da sıralamayı yararlı kılmak için?
MD004

1
@ MD004. Daha iyi bir sıralama dizini oluşturur. Alan aynı kalır.
bigh_29

5

Bunun karşısında tökezleyenler için, şimdi Percona'nın araştırmasına göre çok daha iyi bir alternatif var.

Optimal indeksleme için UUID parçalarının yeniden düzenlenmesinden ve daha az depolama için ikili dosyaya dönüştürülmesinden oluşur.

Makalenin tamamını buradan okuyun


Bu makaleyi daha önce okumuştum. Çok ilginç buluyorum, ancak ikili bir kimliğe göre filtrelemek istiyorsak nasıl bir sorgu gerçekleştirmeliyiz? Sanýrým tekrar alçaltmalýyýz ve sonra kriterleri uygulamalýyýz. Bu kadar zorlayıcı mı? Neden 8 bayt bigint yerine binary (16) (varchar (36) 'dan daha iyi olduğundan emin) depolamalıyız?
Maximus Decimus


fwiw, UUIDv4 tamamen rastgele ve parçalamaya gerek yok.
Mahmoud Al-Qudsi

2

@ Bigh_29 tarafından belirtilenler kılavuzlarımı yenilerine dönüştürdüğünden (anlamadığım nedenlerden dolayı) aşağıdaki işlevleri kullanmanızı öneririm. Ayrıca, bunlar tablolarımda yaptığım testlerde biraz daha hızlı. 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

standart GUID olarak biçimlendirilmiş bir char / varchar değeriniz varsa, basit CAST (BINARY16 olarak MyString AS) kullanarak tüm CONCAT + SUBSTR'ın akıl almaz dizileri olmadan bunu BINARY (16) olarak depolayabilirsiniz.

BINARY (16) alanları, dizelerden çok daha hızlı karşılaştırılır / sıralanır / dizine eklenir ve ayrıca veritabanında iki kat daha az yer kaplar


2
Bu sorgu çalıştırıldığında CAST, uuid dizesini ASCII baytlarına dönüştürdüğünü gösterir: set @a = uuid (); @a, hex'i seçin (cast (@a BINARY (16) olarak)); 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (biçimlendirme için boşluk eklendi). 0x31 = ascii 1, 0x36 = ascii 6. Hatta tire olan 0x2D alıyoruz. Bu, yalnızca kılavuzu bir dize olarak depolamaktan çok farklı değildir, ancak dizeyi makineye özgü kimliğin parçasını temizleyen 16. karakterde kısaltmanız gerekir.
bigh_29

Evet, bu sadece bir kısaltma. select CAST("hello world, this is as long as uiid" AS BINARY(16));üretirhello world, thi
MD004
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.