Bagaimana saya harus menyimpan GUID di tabel MySQL?

146

Apakah saya menggunakan varchar (36) atau apakah ada cara yang lebih baik untuk melakukannya?

CDR
sumber
1
"thaBadDawg" menawarkan jawaban yang bagus. Ada utas paralel tentang Stack Overflow yang membahas topik. Saya menambahkan beberapa komentar ke utas jawaban yang menautkan ke sumber daya dengan lebih detail. Inilah tautan pertanyaannya: stackoverflow.com/questions/547118/storing-mysql-guid-uuids - Saya berharap topik ini menjadi lebih umum ketika orang mulai mempertimbangkan AWS dan Aurora.
Zack Jannsen

Jawaban:

104

DBA saya bertanya ketika saya bertanya tentang cara terbaik untuk menyimpan GUID untuk objek saya mengapa saya perlu menyimpan 16 byte ketika saya bisa melakukan hal yang sama dalam 4 byte dengan Integer. Karena dia memberikan tantangan itu kepada saya, saya pikir sekarang adalah waktu yang tepat untuk menyebutkannya. Yang telah dibilang...

Anda dapat menyimpan panduan sebagai biner CHAR (16) jika Anda ingin memanfaatkan ruang penyimpanan yang paling optimal.

thaBadDawg
sumber
176
Karena dengan 16 byte, Anda dapat membuat berbagai hal dalam database yang berbeda, pada mesin yang berbeda, pada waktu yang berbeda, dan masih menggabungkan data bersama dengan mulus :)
Billy ONeal
4
perlu dibalas, apa sebenarnya biner char 16? bukan char? bukan biner? Saya tidak melihat itu ketik alat gui mysql, atau dokumentasi di situs mysql. @BillyONeal
nawfal
3
@nawfal: Char adalah tipe data. BINARY adalah specifier tipe terhadap tipe. Satu-satunya efek yang dimilikinya adalah memodifikasi cara MySQL melakukan collation. Lihat dev.mysql.com/doc/refman/5.0/id/charset-binary-op.html untuk lebih jelasnya. Tentu saja Anda bisa langsung menggunakan tipe BINARY jika alat pengeditan database memungkinkan Anda melakukannya. (Alat yang lebih tua tidak tahu tipe data biner tetapi tahu bendera kolom biner)
Billy ONeal
2
bidang CHAR dan BINARY pada dasarnya sama. Jika Anda ingin membawanya ke level paling dasar, CHAR adalah bidang biner yang mengharapkan nilai 0 hingga 255 dengan maksud mewakili nilai tersebut dengan nilai yang dipetakan dari tabel pencarian (dalam kebanyakan kasus sekarang, UTF8). Bidang BINARY mengharapkan jenis nilai yang sama tanpa maksud untuk mewakili data tersebut dari tabel pencarian. Saya menggunakan CHAR (16) pada hari-hari 4.x karena saat itu MySQL tidak sebagus sekarang.
thaBadDawg
15
Ada beberapa alasan bagus mengapa GUID jauh lebih baik daripada peningkatan otomatis. Jeff Atwood mendaftar yang ini . Bagi saya, keuntungan terbaik dalam menggunakan GUID adalah bahwa aplikasi saya tidak perlu bolak-balik basis data untuk mengetahui kunci suatu entitas: Saya dapat mengisinya secara terprogram, yang tidak dapat saya lakukan jika saya menggunakan bidang kenaikan-otomatis. Ini menyelamatkan saya dari beberapa sakit kepala: dengan GUID saya dapat mengelola entitas dengan cara yang sama, terlepas dari entitas yang sudah ada atau yang baru.
Arialdo Martini
48

Saya akan menyimpannya sebagai char (36).

Brian Fisher
sumber
5
Saya tidak mengerti mengapa Anda harus menyimpannya -.
Afshin Mehrabani
2
@AfshinMehrabani Sederhana, lugas, mudah dibaca manusia. Itu tidak perlu, tentu saja, tetapi jika menyimpan byte tambahan itu tidak ada salahnya, ini adalah solusi terbaik.
user1717828
2
Menyimpan tanda hubung mungkin bukan ide yang baik karena akan menyebabkan lebih banyak overhead. Jika Anda ingin menjadikannya dapat dibaca manusia, buat aplikasi dibaca dengan tanda hubung.
Lucca Ferri
@AfshinMehrabani pertimbangan lain adalah menguraikannya dari database. Sebagian besar implementasi akan mengharapkan tanda hubung dalam panduan yang valid.
Ryan Gates
Anda dapat memasukkan tanda hubung saat mengambil untuk mengubah char (32) menjadi char (36) dengan mudah. gunakan FN Masukkan mySql.
joedotnot
33

Menambah jawaban oleh ThaBadDawg, gunakan fungsi-fungsi praktis ini (terima kasih kepada kolega saya yang lebih bijak) untuk mendapatkan dari string 36 panjang kembali ke array byte 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)sebenarnya adalah BINARY(16), pilih rasa yang Anda sukai

Untuk mengikuti kode dengan lebih baik, ambil contoh yang diberikan GUID yang dipesan dengan digit di bawah ini. (Karakter ilegal digunakan untuk tujuan ilustrasi - setiap tempat karakter unik.) Fungsi akan mengubah urutan byte untuk mencapai urutan bit untuk pengelompokan indeks superior. Panduan yang diperintahkan ditampilkan di bawah contoh.

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

Tanda hubung dihapus:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW
KCD
sumber
Inilah GuidToBinary di atas tanpa menghapus tanda hubung dari string: CIPTAKAN FUNGSI GuidToBinary($ guid char (36)) RETURNS biner (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
Bagi yang penasaran, fungsi-fungsi ini lebih unggul dari hanya UNHEX (REPLACE (UUID (), '-', '')) karena mengatur bit dalam urutan yang akan berkinerja lebih baik dalam indeks berkerumun.
Slashterix
Ini sangat membantu, tetapi saya merasa ini dapat ditingkatkan dengan sumber CHARdan BINARYkesetaraan ( dokumen tampaknya menyiratkan ada perbedaan penting dan penjelasan mengapa kinerja indeks berkerumun lebih baik dengan byte yang disusun ulang.
Patrick M
Ketika saya menggunakan ini panduan saya berubah. Saya sudah mencoba memasukkannya menggunakan kedua unhex (ganti (string, '-', '')) dan fungsi di atas dan ketika saya mengubahnya kembali menggunakan metode yang sama panduan yang dipilih bukan yang dimasukkan. Apa yang mengubah pedoman? Yang saya lakukan hanyalah menyalin kode dari atas.
vsdev
@JonathanOliver Bisakah Anda berbagi kode untuk fungsi BinaryToGuid ()?
Arun Avanathan
27

char (36) akan menjadi pilihan yang bagus. Juga fungsi UUID () MySQL dapat digunakan yang mengembalikan format teks 36 karakter (heks dengan tanda hubung) yang dapat digunakan untuk pengambilan ID tersebut dari db.

Belajar
sumber
19

"Lebih baik" tergantung pada apa yang Anda optimalkan.

Seberapa besar Anda peduli dengan ukuran / kinerja penyimpanan vs kemudahan pengembangan? Lebih penting - apakah Anda menghasilkan cukup GUID, atau cukup sering mengambilnya, sehingga itu penting?

Jika jawabannya "tidak", char(36)lebih dari cukup baik, dan itu membuat penyimpanan / pengambilan GUID menjadi sangat sederhana. Kalau tidak, binary(16)masuk akal, tetapi Anda harus bersandar pada MySQL dan / atau bahasa pemrograman pilihan Anda untuk mengkonversi bolak-balik dari representasi string yang biasa.

candu
sumber
2
Jika Anda meng-host perangkat lunak (misalnya halaman web) dan tidak menjual / menginstal di klien, Anda selalu dapat memulai dengan char (36) untuk pengembangan mudah pada tahap awal perangkat lunak, dan bermutasi ke yang lebih ringkas format sebagai sistem tumbuh dalam penggunaan dan mulai membutuhkan optimasi.
Xavi Montero
1
Sisi buruk terbesar dari char yang jauh lebih besar (36) adalah seberapa banyak ruang yang akan diambil oleh indeks. Jika Anda memiliki sejumlah besar catatan dalam database, Anda menggandakan ukuran indeks.
bpeikes
8

Binary (16) akan lebih baik, lebih baik daripada penggunaan varchar (32).

Onkar Janwa
sumber
7

Rutin GuidToBinary yang diposting oleh KCD harus disesuaikan untuk memperhitungkan tata letak bit stempel waktu dalam string GUID. Jika string mewakili UUID versi 1, seperti yang dikembalikan oleh rutin uuid () mysql, maka komponen waktu disematkan dalam huruf 1-G, tidak termasuk 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

Ketika Anda mengonversi ke biner, urutan terbaik untuk pengindeksan adalah: EFG9ABC12345678D + sisanya.

Anda tidak ingin menukar 12345678 ke 78563412 karena big endian sudah menghasilkan urutan byte indeks biner terbaik. Namun, Anda ingin byte yang paling signifikan dipindahkan di depan byte yang lebih rendah. Oleh karena itu, EFG pergi dulu, diikuti oleh bit tengah dan bit rendah. Hasilkan selusin UUID dengan uuid () selama satu menit dan Anda akan melihat bagaimana pesanan ini menghasilkan peringkat yang benar.

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 

Dua UUID pertama dihasilkan paling mendekati waktu. Mereka hanya bervariasi dalam 3 gigitan terakhir dari blok pertama. Ini adalah bit paling tidak signifikan dari cap waktu, yang berarti kita ingin mendorong mereka ke kanan ketika kita mengonversinya ke array byte yang dapat diindeks. Sebagai contoh balasan, ID terakhir adalah yang terbaru, tetapi algoritma swapping KCD akan menempatkannya di depan ID ke-3 (3e sebelum dc, byte terakhir dari blok pertama).

Urutan pengindeksan yang benar adalah:

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

Lihat artikel ini untuk informasi pendukung: http://mysql.rjweb.org/doc.php/uuid

Perhatikan bahwa saya tidak membagi nibble versi dari 12 bit cap waktu yang tinggi. Ini adalah gigitan D dari contoh Anda. Saya hanya membuangnya di depan. Jadi urutan biner saya akhirnya menjadi DEFG9ABC dan seterusnya. Ini menyiratkan bahwa semua UUID saya yang diindeks mulai dengan gigitan yang sama. Artikel itu melakukan hal yang sama.

bigh_29
sumber
Apakah tujuan ini untuk menghemat ruang penyimpanan? atau membuat penyortiran mereka berguna?
MD004
1
@ MD004. Ini menciptakan indeks pengurutan yang lebih baik. Ruang tetap sama.
bigh_29
5

Bagi mereka yang hanya tersandung di sini, sekarang ada alternatif yang jauh lebih baik sesuai penelitian oleh Percona.

Ini terdiri dari reorganisasi potongan UUID untuk pengindeksan yang optimal, kemudian dikonversi menjadi biner untuk penyimpanan berkurang.

Baca artikel selengkapnya di sini

sleepycal
sumber
Saya membaca artikel itu sebelumnya. Saya merasa sangat menarik tetapi kemudian bagaimana kita melakukan query jika kita ingin memfilter dengan ID yang biner? Saya kira kita perlu hex lagi dan kemudian menerapkan kriteria. Apakah ini sangat menuntut? Mengapa menyimpan binary (16) (tentu lebih baik daripada varchar (36)) daripada bigint 8 byte?
Maximus Decimus
2
Ada artikel yang diperbarui dari MariaDB yang seharusnya menjawab pertanyaan Anda mariadb.com/kb/en/mariadb/guiduuid-performance
sleepycal
fwiw, UUIDv4 benar-benar acak dan tidak perlu memotong.
Mahmoud Al-Qudsi
2

Saya akan menyarankan menggunakan fungsi di bawah ini karena yang disebutkan oleh @ bigh_29 mengubah panduan saya menjadi yang baru (karena alasan saya tidak mengerti). Juga, ini sedikit lebih cepat dalam tes yang saya lakukan di meja saya. 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
sumber
-4

jika Anda memiliki nilai char / varchar yang diformat sebagai GUID standar, Anda dapat menyimpannya sebagai BINARY (16) menggunakan CAST sederhana (MyString AS BINARY16), tanpa semua urutan CONCAT + SUBSTR yang membingungkan.

BINARY (16) bidang dibandingkan / diurutkan / diindeks jauh lebih cepat daripada string, dan juga mengambil dua kali lebih sedikit ruang dalam database

George Hazan
sumber
2
Menjalankan kueri ini menunjukkan bahwa CAST mengubah string uuid ke ASCII byte: set @a = uuid (); pilih @a, hex (cast (@a AS BINARY (16))); Saya mendapatkan 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (spasi ditambahkan untuk pemformatan). 0x31 = ascii 1, 0x36 = ascii 6. Kita bahkan mendapatkan 0x2D, ​​yang merupakan tanda hubung. Ini tidak jauh berbeda dari hanya menyimpan panduan sebagai string, kecuali bahwa Anda memotong string pada karakter ke-16, yang memotong bagian dari ID yang spesifik mesin.
bigh_29
Ya, ini hanya pemotongan. select CAST("hello world, this is as long as uiid" AS BINARY(16));menghasilkanhello world, thi
MD004