ฉันจะเก็บ GUID ในตาราง MySQL ได้อย่างไร


146

ฉันใช้ varchar (36) หรือมีวิธีที่ดีกว่านี้หรือไม่?


1
"thaBadDawg" เสนอคำตอบที่ดี มีเธรดแบบขนานบน Stack Overflow ที่กล่าวถึงหัวข้อ ฉันได้เพิ่มความคิดเห็นบางส่วนให้กับกระทู้ที่ตอบลิงค์ไปยังแหล่งข้อมูลที่มีรายละเอียดเพิ่มเติม นี่คือลิงค์คำถาม: stackoverflow.com/questions/547118/storing-mysql-guid-uuids - ฉันคาดหวังว่าหัวข้อนี้จะกลายเป็นเรื่องธรรมดามากขึ้นเมื่อผู้คนเริ่มพิจารณา AWS และ Aurora
Zack Jannsen

คำตอบ:


104

DBA ของฉันถามฉันเมื่อฉันถามเกี่ยวกับวิธีที่ดีที่สุดในการจัดเก็บ GUID สำหรับวัตถุของฉันทำไมฉันต้องเก็บ 16 ไบต์เมื่อฉันสามารถทำสิ่งเดียวกันใน 4 ไบต์ด้วย Integer เนื่องจากเขาใช้ความท้าทายนั้นกับฉันฉันคิดว่าตอนนี้เป็นเวลาที่ดีที่จะพูดถึงมัน ที่ถูกกล่าวว่า ...

คุณสามารถจัดเก็บ guid เป็นไบนารี CHAR (16) หากคุณต้องการใช้พื้นที่เก็บข้อมูลให้เกิดประโยชน์สูงสุด


176
เพราะด้วย 16 ไบต์คุณสามารถสร้างสิ่งต่าง ๆ ในฐานข้อมูลที่แตกต่างกันบนเครื่องที่แตกต่างกันในเวลาที่แตกต่างกันและยังคงรวมข้อมูลเข้าด้วยกันได้อย่างราบรื่น :)
Billy ONeal

4
ต้องการคำตอบอะไรคือไบนารี 16 ถ่านจริงๆ? ไม่ถ่าน? ไม่ใช่ไบนารี? ฉันไม่เห็นประเภทนั้นในเครื่องมือ mysql gui ใด ๆ หรือเอกสารใด ๆ ในเว็บไซต์ mysql @BillyONeal
nawfal

3
@nawfal: Char เป็นประเภทข้อมูล BINARY เป็นตัวระบุชนิดเทียบกับชนิด ผลกระทบเดียวที่มีคือการแก้ไขวิธีที่ MySQL ทำการเรียงหน้า ดูdev.mysql.com/doc/refman/5.0/en/charset-binary-op.htmlสำหรับรายละเอียดเพิ่มเติม แน่นอนคุณสามารถใช้ชนิดไบนารีโดยตรงได้หากเครื่องมือแก้ไขฐานข้อมูลของคุณอนุญาตให้ทำเช่นนั้นได้ (เครื่องมือเก่าไม่ทราบชนิดข้อมูลไบนารี แต่ไม่ทราบว่าของธงคอลัมน์ binary)
บิลลี่ ONeal

2
CHAR และฟิลด์ BINARY นั้นเหมือนกัน หากคุณต้องการที่จะนำไปใช้ในระดับพื้นฐานที่สุด CHAR เป็นเขตข้อมูลไบนารีที่คาดว่าจะมีค่า 0 ถึง 255 โดยมีเจตนาที่จะแสดงค่าดังกล่าวด้วยค่าที่แมปจากตารางการค้นหา (ในกรณีส่วนใหญ่ตอนนี้ UTF8) เขตข้อมูล BINARY คาดหวังมูลค่าแบบเดียวกันโดยไม่ต้องการแสดงข้อมูลดังกล่าวจากตารางการค้นหา ฉันใช้ CHAR (16) ย้อนกลับไปในช่วง 4.x วันเพราะตอนนั้น MySQL ไม่ดีเท่าตอนนี้
thaBadDawg

15
มีสาเหตุหลายประการที่ทำให้ GUID ดีกว่าระบบอัตโนมัติ เจฟฟ์แอดแสดงรายการหนึ่งเหล่านี้ สำหรับฉันข้อได้เปรียบที่ดีที่สุดในการใช้ GUID คือแอปของฉันไม่จำเป็นต้องใช้ฐานข้อมูลไปกลับเพื่อทราบถึงคีย์ของเอนทิตี: ฉันสามารถใส่ข้อมูลลงในโปรแกรมได้ซึ่งฉันไม่สามารถทำได้ถ้าใช้ฟิลด์เพิ่มอัตโนมัติ สิ่งนี้ช่วยให้ฉันประหยัดจากอาการปวดหัวหลายครั้ง: ด้วย GUID ฉันสามารถจัดการเอนทิตีในลักษณะเดียวกันได้ไม่ว่าจะมีเอนทิตียืนยันอยู่แล้วหรือเป็นแบรนด์ใหม่
Arialdo Martini

48

ฉันจะเก็บมันไว้เป็นถ่าน (36)


5
ฉันไม่เห็นว่าทำไมคุณควรเก็บ-ของ
Afshin Mehrabani

2
@AshshMeMeababani มันง่ายตรงไปตรงมามนุษย์สามารถอ่านได้ แน่นอนว่ามันไม่จำเป็น แต่ถ้าการเก็บไบต์พิเศษเหล่านั้นไม่ได้ทำให้เจ็บปวดนี่เป็นทางออกที่ดีที่สุด
user1717828

2
การเก็บเครื่องหมายขีดคั่นอาจไม่ใช่ความคิดที่ดีเพราะจะทำให้เกิดค่าใช้จ่ายมากขึ้น หากคุณต้องการทำให้เป็นมนุษย์อ่านได้ทำให้แอปพลิเคชันอ่านด้วยขีดกลาง
Lucca Ferri

@AfshinMehrabani ข้อพิจารณาอีกอย่างคือการแยกวิเคราะห์จากฐานข้อมูล การใช้งานส่วนใหญ่จะคาดหวังขีดกลางในแนวทางที่ถูกต้อง
Ryan Gates

คุณสามารถแทรกยัติภังค์เมื่อดึงเพื่อแปลงถ่าน (32) เป็นถ่าน (36) ได้อย่างง่ายดาย ใช้การแทรก FN ของ mySql
joedotnot

33

การเพิ่มคำตอบโดย ThaBadDawg ใช้ฟังก์ชั่นที่มีประโยชน์เหล่านี้ (ขอบคุณเพื่อนร่วมงานที่ชาญฉลาดของฉัน) เพื่อรับจากสตริงความยาว 36 กลับไปเป็นอาร์เรย์ไบต์ที่ 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)เป็นจริงBINARY(16)เลือกรสชาติที่คุณต้องการ

หากต้องการติดตามโค้ดให้ดีขึ้นนำตัวอย่างที่ระบุให้กับ GUID ที่สั่งซื้อด้วยหลักด้านล่าง (อักขระที่ผิดกฎหมายถูกใช้เพื่อจุดประสงค์ในการอธิบาย - แต่ละที่มีอักขระที่ไม่ซ้ำกัน) ฟังก์ชันจะแปลงลำดับไบต์เพื่อให้ได้ลำดับบิตสำหรับการจัดกลุ่มดัชนีที่ดีกว่า guid ที่จัดลำดับใหม่แสดงไว้ด้านล่างตัวอย่าง

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

ลบเครื่องหมายขีดคั่น:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW

นี่คือ GuidToBinary ข้างต้นโดยไม่ต้องลบเครื่องหมายยัติภังค์ออกจากสตริง: สร้างฟังก์ชั่นGuidToBinary($ guid char (36)) ผลตอบแทนไบนารี (16) กลับมาอีกครั้ง CONCAT (UNHEX (SUBSTRING ($ guid, 7, 2)) 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
สำหรับสิ่งที่อยากรู้อยากเห็นฟังก์ชั่นเหล่านี้ดีกว่าเพียงแค่ UNHEX (REPLACE (UUID (), '-', '')) เพราะมันจัดเรียงบิตตามลำดับที่จะทำงานได้ดีขึ้นในดัชนีคลัสเตอร์
Slashterix

สิ่งนี้มีประโยชน์มาก แต่ฉันรู้สึกว่ามันสามารถปรับปรุงได้ด้วยแหล่งที่มาCHARและความBINARYเท่าเทียมกัน ( เอกสารดูเหมือนจะบ่งบอกว่ามีความแตกต่างที่สำคัญและคำอธิบายว่าทำไมดัชนีดัชนีแบบกลุ่มจึงดีกว่าด้วยไบต์ที่เรียงลำดับใหม่
Patrick M

เมื่อฉันใช้สิ่งนี้ guid ของฉันก็เปลี่ยนไป ฉันได้ลองใส่มันโดยใช้ unhex (แทนที่ (string, '-', '')) และฟังก์ชั่นด้านบนและเมื่อฉันแปลงมันกลับมาโดยใช้วิธีการเดียวกัน guid ที่เลือกไม่ได้ถูกแทรกเข้าไป การเปลี่ยนแนวทางคืออะไร สิ่งที่ฉันทำเสร็จแล้วคือการคัดลอกโค้ดจากด้านบน
vsdev

@JonathanOliver คุณช่วยแบ่งปันรหัสสำหรับฟังก์ชั่น BinaryToGuid () ได้ไหม?
อรุณอวานาธาน

27

ถ่าน (36) จะเป็นทางเลือกที่ดี นอกจากนี้ยังสามารถใช้ฟังก์ชั่น UUID () ของ MySQL ซึ่งส่งกลับรูปแบบข้อความ 36 ตัวอักษร (ฐานสิบหกพร้อมยัติภังค์) ซึ่งสามารถใช้สำหรับการดึงข้อมูล ID ดังกล่าวจาก db


19

"ดีกว่า" ขึ้นอยู่กับสิ่งที่คุณปรับให้เหมาะสม

ขนาดของคุณ / ประสิทธิภาพการจัดเก็บเทียบกับความสะดวกในการพัฒนาเท่าไหร่ สำคัญกว่า - คุณสร้าง GUID ให้เพียงพอหรือดึงข้อมูลเหล่านั้นบ่อยเพียงพอหรือไม่

หากคำตอบคือ "ไม่" char(36)ก็ดีเกินพอและมันทำให้การจัดเก็บ / ดึงข้อมูล GUIDs นั้นตายง่าย มิฉะนั้นbinary(16)ก็สมเหตุสมผล แต่คุณจะต้องพึ่งพา MySQL และ / หรือภาษาการเขียนโปรแกรมที่คุณเลือกเพื่อแปลงไปมาจากการแสดงสตริงปกติ


2
หากคุณโฮสต์ซอฟต์แวร์ (เช่นหน้าเว็บตัวอย่าง) และไม่ขาย / ติดตั้งในไคลเอนต์คุณสามารถเริ่มต้นด้วย char (36) เพื่อการพัฒนาที่ง่ายในระยะแรกของซอฟต์แวร์และกลายพันธุ์ให้กะทัดรัดยิ่งขึ้น รูปแบบเมื่อระบบเติบโตในการใช้งานและเริ่มต้องการการปรับให้เหมาะสม
Xavi Montero

1
ด้านที่ใหญ่ที่สุดของ char ที่มีขนาดใหญ่กว่ามาก (36) คือพื้นที่ที่ดัชนีใช้ ถ้าคุณมีบันทึกจำนวนมากในฐานข้อมูลคุณจะเพิ่มขนาดของดัชนีเป็นสองเท่า
bpeikes


7

GuidToBinary ขั้นตอนการโพสต์โดย KCD ควร tweaked บัญชีสำหรับรูปแบบบิตของการประทับเวลาในสตริง GUID หากสตริงแสดงถึง UUID เวอร์ชัน 1 เช่นเดียวกับที่ส่งคืนโดยรูทีน uuid () mysql ดังนั้นคอมโพเนนต์เวลาจะถูกฝังในตัวอักษร 1-G โดยไม่รวมตัว 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

เมื่อคุณแปลงเป็นไบนารีลำดับที่ดีที่สุดสำหรับการจัดทำดัชนีคือ: EFG9ABC12345678D + ส่วนที่เหลือ

คุณไม่ต้องการสลับ 12345678 ถึง 78563412 เนื่องจาก endian ใหญ่ให้ผลดัชนีไบท์ดัชนีไบต์ที่ดีที่สุดแล้ว อย่างไรก็ตามคุณต้องการให้ไบต์ที่สำคัญที่สุดย้ายไปอยู่ด้านหน้าไบต์ที่ต่ำกว่า ดังนั้น EFG จะไปก่อนตามด้วยบิตกลางและบิตต่ำกว่า สร้างโหลหรือมากกว่านั้นด้วย UUID () ในเวลาไม่กี่นาทีและคุณควรดูว่าคำสั่งนี้ให้อันดับที่ถูกต้องอย่างไร

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 

UUID สองตัวแรกถูกสร้างขึ้นใกล้เคียงกับเวลา พวกเขาแตกต่างกันใน 3 nibbles สุดท้ายของบล็อกแรกเท่านั้น นี่เป็นบิตที่มีนัยสำคัญน้อยที่สุดของการประทับเวลาซึ่งหมายความว่าเราต้องการผลักดันมันไปทางขวาเมื่อเราแปลงมันเป็นอาร์เรย์ไบต์ที่ทำดัชนีได้ เป็นตัวอย่างตัวนับ ID สุดท้ายเป็นข้อมูลล่าสุด แต่อัลกอริทึมการสลับของ KCD จะใส่ไว้ก่อน ID 3 (3e ก่อน dc, ไบต์สุดท้ายจากบล็อกแรก)

ลำดับที่ถูกต้องสำหรับการจัดทำดัชนีจะเป็น:

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

ดูบทความนี้สำหรับข้อมูลสนับสนุน: http://mysql.rjweb.org/doc.php/uuid

*** โปรดทราบว่าฉันไม่แยกแทะเวอร์ชันออกจากการประทับเวลาสูง 12 บิต นี่คือตอด D จากตัวอย่างของคุณ ฉันแค่โยนมันไปด้านหน้า ดังนั้นเลขฐานสองของฉันก็คือ DEFG9ABC เป็นต้น นี่ก็หมายความว่า UUID ที่ทำดัชนีของฉันทั้งหมดเริ่มต้นด้วยแทะเดียวกัน บทความทำสิ่งเดียวกัน


นี้มีวัตถุประสงค์เพื่อประหยัดพื้นที่จัดเก็บ? หรือเพื่อให้การเรียงลำดับมีประโยชน์
MD004

1
@ MD004 มันสร้างดัชนีการเรียงลำดับที่ดีขึ้น พื้นที่ยังคงเหมือนเดิม
bigh_29

5

สำหรับผู้ที่เพิ่งสะดุดกับเรื่องนี้ตอนนี้มีทางเลือกที่ดีกว่ามากตามการวิจัยโดย Percona

ประกอบด้วยการจัดระเบียบ UUID ชิ้นใหม่สำหรับการจัดทำดัชนีที่เหมาะสมจากนั้นแปลงเป็นไบนารีเพื่อลดการจัดเก็บ

อ่านบทความเต็มได้ที่นี่


ฉันอ่านบทความนั้นมาก่อน ฉันคิดว่ามันน่าสนใจมาก แต่ถ้าอย่างนั้นเราควรจะทำการสืบค้นอย่างไรถ้าเราต้องการกรองโดยใช้ ID ซึ่งเป็นเลขฐานสอง? ฉันเดาว่าเราต้องทำ hex อีกครั้งแล้วใช้เกณฑ์ มันเป็นความต้องการหรือไม่ ทำไมต้องจัดเก็บไบนารี่ (16) (ตรวจสอบว่าดีกว่า varchar (36)) แทนที่จะเป็น 8 ไบต์ใหญ่?
Maximus Decimus

2
มีบทความที่อัปเดตจาก MariaDB ซึ่งควรตอบคำถามของคุณmariadb.com/kb/en/mariadb/guiduuid-performance
sleepycal

fwiw, UUIDv4 เป็นแบบสุ่มสมบูรณ์และไม่จำเป็นต้องมีการสับ
Mahmoud Al-Qudsi

2

ฉันขอแนะนำให้ใช้ฟังก์ชั่นด้านล่างนี้เนื่องจากสิ่งที่ @ bigh_29 พูดถึงเปลี่ยน guids ของฉันเป็นสิ่งใหม่ (ด้วยเหตุผลที่ฉันไม่เข้าใจ) นอกจากนี้สิ่งเหล่านี้ก็เร็วขึ้นเล็กน้อยในการทดสอบที่ฉันทำบนโต๊ะของฉัน 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

หากคุณมีค่า char / varchar จัดรูปแบบเป็น GUID มาตรฐานคุณสามารถเก็บเป็น BINARY (16) โดยใช้ CAST แบบง่าย (MyString AS BINARY16) โดยไม่มีลำดับที่น่าเหลือเชื่อของ CONCAT + SUBSTR

ฟิลด์ BINARY (16) จะถูกเปรียบเทียบ / เรียงลำดับ / จัดทำดัชนีเร็วกว่าสตริงมากและยังใช้พื้นที่น้อยลงสองเท่าในฐานข้อมูล


2
การรันเคียวรีนี้แสดงว่า CAST แปลงสตริง uuid เป็น ASCII bytes: set @a = uuid (); เลือก @ a, hex (cast (@a AS BINARY (16))); ฉันได้รับ 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (เพิ่มช่องว่างสำหรับการจัดรูปแบบ) 0x31 = ascii 1, 0x36 = ascii 6. เรายังได้ 0x2D ซึ่งเป็นเครื่องหมายยัติภังค์ สิ่งนี้ไม่แตกต่างจากการจัดเก็บ guid เป็นสตริงยกเว้นว่าคุณจะตัดทอนสตริงที่อักขระ 16th ซึ่งจะตัดส่วนของ ID ที่เป็นเครื่องเฉพาะออก
bigh_29

ใช่นี่เป็นแค่การตัดทอน select CAST("hello world, this is as long as uiid" AS BINARY(16));ผลิตhello world, thi
MD004
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.