Tôi nên lưu trữ GUID trong các bảng MySQL như thế nào?


146

Tôi có sử dụng varchar (36) hay có cách nào tốt hơn để làm điều đó không?


1
"thaBadDawg" cung cấp một câu trả lời tốt. Có một luồng song song trên Stack Overflow thảo luận về chủ đề này. Tôi đã thêm một số ý kiến ​​cho chủ đề đó trả lời liên kết đến các tài nguyên với nhiều chi tiết hơn. Đây là liên kết câu hỏi: stackoverflow.com/questions/547118/storing-mysql-guid-uuids - Tôi hy vọng chủ đề này sẽ trở nên phổ biến hơn khi mọi người bắt đầu xem xét AWS và Aurora.
Zack Jannsen 4/2/2016

Câu trả lời:


104

DBA của tôi đã hỏi tôi khi tôi hỏi về cách tốt nhất để lưu GUID cho các đối tượng của mình tại sao tôi cần lưu trữ 16 byte khi tôi có thể làm điều tương tự trong 4 byte với một Integer. Kể từ khi anh ấy đưa thử thách đó ra cho tôi, tôi nghĩ bây giờ là thời điểm tốt để đề cập đến nó. Điều đó đang được nói ...

Bạn có thể lưu trữ một hướng dẫn dưới dạng nhị phân CHAR (16) nếu bạn muốn sử dụng không gian lưu trữ tối ưu nhất.


176
Bởi vì với 16 byte, bạn có thể tạo mọi thứ trong các cơ sở dữ liệu khác nhau, trên các máy khác nhau, vào các thời điểm khác nhau và vẫn hợp nhất dữ liệu với nhau một cách liền mạch :)
Billy ONeal

4
cần trả lời, thực sự là một nhị phân char 16 là gì? không phải là char? không nhị phân? Tôi không thấy kiểu đó trong bất kỳ công cụ gui mys nào, cũng như bất kỳ tài liệu nào trong trang web mysql. @BillyONeal
nawfal

3
@nawfal: Char là kiểu dữ liệu. BINary là công cụ xác định kiểu so với loại. Hiệu ứng duy nhất của nó là sửa đổi cách thức đối chiếu của MySQL. Xem dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html để biết thêm chi tiết. Tất nhiên bạn chỉ có thể sử dụng một loại BINary trực tiếp nếu công cụ chỉnh sửa cơ sở dữ liệu của bạn cho phép bạn làm điều đó. (Các công cụ cũ hơn không biết về kiểu dữ liệu nhị phân nhưng biết về cờ cột nhị phân)
Billy ONeal

2
một trường CHAR và một trường BINary về cơ bản là giống nhau. Nếu bạn muốn đưa nó đến mức cơ bản nhất, CHAR là trường nhị phân mong đợi giá trị 0 đến 255 với ý định biểu thị giá trị đã nói với giá trị được ánh xạ từ bảng tra cứu (trong hầu hết các trường hợp hiện nay, UTF8). Trường BINary mong đợi cùng loại giá trị mà không có ý định biểu thị dữ liệu đã nói từ bảng tra cứu. Tôi đã sử dụng CHAR (16) trở lại trong 4.x ngày vì hồi đó MySQL không còn tốt như bây giờ.
thaBadDawg

15
Có một số lý do chính đáng tại sao GUID tốt hơn nhiều so với tự động. Jeff Atwood liệt kê những thứ này . Đối với tôi, lợi thế tốt nhất khi sử dụng GUID là ứng dụng của tôi sẽ không cần làm tròn cơ sở dữ liệu để biết khóa của một thực thể: Tôi có thể điền nó theo chương trình, điều mà tôi không thể làm nếu tôi đang sử dụng trường tăng tự động. Điều này đã cứu tôi khỏi một số vấn đề đau đầu: với GUID tôi có thể quản lý thực thể theo cùng một cách, bất kể thực thể đó đã được duy trì hay nó là một thương hiệu mới.
Arialdo Martini

48

Tôi sẽ lưu trữ nó như một char (36).


5
Tôi không thể thấy lý do tại sao bạn nên lưu trữ -s.
Afshin Mehrabani

2
@AfshinMehrabani Thật đơn giản, dễ hiểu, dễ đọc với con người. Tất nhiên, điều đó không cần thiết, nhưng nếu việc lưu trữ các byte thừa đó không gây hại thì đây là giải pháp tốt nhất.
dùng1717828

2
Lưu trữ dấu gạch ngang có thể không phải là một ý tưởng tốt bởi vì nó sẽ gây ra nhiều chi phí hơn. Nếu bạn muốn làm cho nó dễ đọc hơn, hãy làm cho ứng dụng đọc bằng dấu gạch ngang.
Lucca Ferri

@AfshinMehrabani xem xét khác là phân tích cú pháp từ cơ sở dữ liệu. Hầu hết các triển khai sẽ mong đợi dấu gạch ngang trong một hướng dẫn hợp lệ.
Ryan Gates

Bạn có thể chèn dấu gạch nối khi tìm nạp để chuyển đổi char (32) thành char (36) một cách dễ dàng. sử dụng Chèn FN của mySql.
joedotnot

33

Thêm vào câu trả lời của ThaBadDawg, sử dụng các hàm tiện dụng này (nhờ một đồng nghiệp khôn ngoan hơn của tôi) để lấy từ chuỗi 36 chiều dài trở lại một mảng 16 byte.

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)thực sự là một BINARY(16), chọn hương vị ưa thích của bạn

Để làm theo mã tốt hơn, hãy lấy ví dụ đưa ra GUID theo thứ tự chữ số bên dưới. (Các ký tự bất hợp pháp được sử dụng cho mục đích minh họa - mỗi nơi đặt một ký tự duy nhất.) Các hàm sẽ chuyển đổi thứ tự byte để đạt được thứ tự bit cho phân cụm chỉ mục vượt trội. Hướng dẫn sắp xếp lại được hiển thị dưới ví dụ.

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

Dấu gạch ngang bị xóa:

123456789ABCDEFGHIJKLMNOPQRSTUVW
78563412BC9AFGDEHIJKLMNOPQRSTUVW

Đây là GuidToBinary ở trên mà không loại bỏ các dấu gạch nối khỏi chuỗi: TẠO CHỨC NĂNG GuidToBinary($ guide char (36)) TRẢ LẠI nhị phân (16) RETURN CONCAT (UNHEX (SUBSTRING ($ guide, 7, 2)), UNHEX (SUBSTRING ($) 5, 2)), UNHEX (SUBSTRING ($ guide, 3, 2)), UNHEX (SUBSTRING ($ guide, 1, 2)), UNHEX (SUBSTRING ($ guide, 12, 2)), UNHEX (SUBSTRING ($ guide, 10, 2)), UNHEX (SUBSTRING ($ guide, 17, 2)), UNHEX (SUBSTRING ($ guide, 15, 2)), UNHEX (SUBSTRING ($ guide, 20, 4)), UNHEX (SUBSTRING (hướng dẫn $, 25, 12)));
Jonathan Oliver

4
Đối với người tò mò, các hàm này vượt trội hơn so với chỉ UNHEX (REPLACE (UUID (), '-', '')) vì nó sắp xếp các bit theo thứ tự sẽ hoạt động tốt hơn trong một chỉ mục được nhóm.
Slashterix

Điều này rất hữu ích, nhưng tôi cảm thấy nó có thể được cải thiện với một nguồn cho CHARBINARYtương đương ( các tài liệu dường như ngụ ý có sự khác biệt quan trọng và giải thích về hiệu suất chỉ số lý do tại sao nhóm là tốt hơn với byte sắp xếp lại.
Patrick M

Khi tôi sử dụng hướng dẫn này của tôi được thay đổi. Tôi đã thử chèn nó bằng cả hai phương thức (thay thế (chuỗi, '-', '')) và chức năng ở trên và khi tôi chuyển đổi chúng trở lại bằng cùng một phương thức, hướng dẫn được chọn không phải là phương thức được chèn. Điều gì đang chuyển đổi hướng dẫn? Tất cả những gì tôi đã làm được sao chép mã từ trên.
vsdev

@JonathanOliver Bạn có thể vui lòng chia sẻ mã cho hàm BinaryToGuid () không?
Arun Avanathan 16/03/19

27

char (36) sẽ là một lựa chọn tốt. Ngoài ra, hàm UUID () của MySQL có thể được sử dụng để trả về định dạng văn bản 36 ký tự (hex có dấu gạch nối) có thể được sử dụng để truy xuất các ID đó từ db.


19

"Tốt hơn" phụ thuộc vào những gì bạn đang tối ưu hóa.

Bao nhiêu bạn quan tâm về kích thước / hiệu suất lưu trữ so với dễ phát triển? Quan trọng hơn - bạn có tạo đủ GUID hoặc tìm nạp chúng thường xuyên đủ không, điều đó có quan trọng không?

Nếu câu trả lời là "không", char(36)thì quá đủ tốt và nó làm cho việc lưu trữ / tìm nạp GUID trở nên đơn giản. Mặt khác, binary(16)là hợp lý, nhưng bạn sẽ phải dựa vào MySQL và / hoặc ngôn ngữ lập trình bạn chọn để chuyển đổi qua lại từ cách biểu diễn chuỗi thông thường.


2
Nếu bạn lưu trữ phần mềm (ví dụ: trang web chẳng hạn) và không bán / cài đặt trong máy khách, bạn luôn có thể bắt đầu với char (36) để dễ dàng phát triển trong giai đoạn đầu của phần mềm và biến đổi thành gọn hơn định dạng khi hệ thống phát triển trong việc sử dụng và bắt đầu cần tối ưu hóa.
Xavi Montero

1
Mặt trái lớn nhất của char lớn hơn nhiều (36) là chỉ số sẽ chiếm bao nhiêu dung lượng. Nếu bạn có số lượng lớn các bản ghi trong cơ sở dữ liệu, bạn đang nhân đôi kích thước của chỉ mục.
bpeike

8

Nhị phân (16) sẽ ổn, tốt hơn so với sử dụng varchar (32).


7

Thường trình GuidToBinary được đăng bởi KCD nên được điều chỉnh để tính đến bố cục bit của dấu thời gian trong chuỗi GUID. Nếu chuỗi đại diện cho UUID phiên bản 1, giống như chuỗi được trả về bởi thường trình mysu uuid (), thì các thành phần thời gian được nhúng trong các chữ cái 1-G, ngoại trừ 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

Khi bạn chuyển đổi thành nhị phân, thứ tự tốt nhất để lập chỉ mục sẽ là: EFG9ABC12345678D + phần còn lại.

Bạn không muốn trao đổi 12345678 thành 78563412 vì endian lớn đã mang lại thứ tự byte chỉ số nhị phân tốt nhất. Tuy nhiên, bạn muốn các byte quan trọng nhất được di chuyển trước các byte thấp hơn. Do đó, EFG đi trước, tiếp theo là bit giữa và bit thấp hơn. Tạo ra một tá UUID với uuid () trong vòng một phút và bạn sẽ thấy thứ tự này mang lại thứ hạng chính xác như thế nào.

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 

Hai UUID đầu tiên được tạo ra gần nhất theo thời gian. Chúng chỉ khác nhau trong 3 ngòi cuối cùng của khối đầu tiên. Đây là các bit có ý nghĩa ít nhất của dấu thời gian, có nghĩa là chúng ta muốn đẩy chúng sang phải khi chúng ta chuyển đổi nó thành một mảng byte có thể lập chỉ mục. Như một ví dụ ngược lại, ID cuối cùng là mới nhất, nhưng thuật toán hoán đổi của KCD sẽ đặt nó trước ID thứ 3 (3e trước dc, các byte cuối từ khối đầu tiên).

Thứ tự đúng để lập chỉ mục sẽ là:

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

Xem bài viết này để biết thông tin hỗ trợ: http://mysql.rjweb.org/doc.php/uuid

*** lưu ý rằng tôi không tách phiên bản nibble từ 12 bit cao của dấu thời gian. Đây là D nibble từ ví dụ của bạn. Tôi chỉ cần ném nó ở phía trước. Vì vậy, chuỗi nhị phân của tôi kết thúc là DEFG9ABC và cứ thế. Điều này ngụ ý rằng tất cả các UUID được lập chỉ mục của tôi bắt đầu với cùng một ngòi nổ. Bài báo làm điều tương tự.


mục đích của việc này là để tiết kiệm không gian lưu trữ? hoặc để làm cho sắp xếp chúng hữu ích?
MD004

1
@ MD004. Nó tạo ra một chỉ số sắp xếp tốt hơn. Không gian vẫn như cũ.
bigh_29

5

Đối với những người chỉ vấp phải điều này, giờ đây đã có một sự thay thế tốt hơn nhiều theo nghiên cứu của Percona.

Nó bao gồm việc sắp xếp lại các khối UUID để lập chỉ mục tối ưu, sau đó chuyển đổi thành nhị phân để giảm dung lượng.

Đọc toàn bộ bài báo ở đây


Tôi đã đọc bài viết đó trước đây. Tôi thấy nó rất thú vị nhưng sau đó chúng ta nên thực hiện một truy vấn như thế nào nếu chúng ta muốn lọc theo một ID là nhị phân? Tôi đoán chúng ta cần hex lại và sau đó áp dụng các tiêu chí. Có quá khắt khe không? Tại sao để lưu trữ nhị phân (16) (chắc chắn rằng nó tốt hơn varchar (36)) thay vì bigint 8 byte?
Maximus Decimus

2
Có một bài viết được cập nhật từ MariaDB sẽ trả lời câu hỏi của bạn mariadb.com/kb/en/mariadb/guiduuid-performance
Sleepycal

fwiw, UUIDv4 là hoàn toàn ngẫu nhiên và không cần chunking.
Mahmoud Al-Qudsi

2

Tôi sẽ đề nghị sử dụng các hàm bên dưới vì các hàm được đề cập bởi @ bigh_29 chuyển đổi các hướng dẫn của tôi thành các hàm mới (vì lý do tôi không hiểu). Ngoài ra, đây là nhanh hơn một chút trong các thử nghiệm tôi đã làm trên bàn của mình. 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

nếu bạn có giá trị char / varchar được định dạng là GUID tiêu chuẩn, bạn chỉ cần lưu trữ nó dưới dạng BINary (16) bằng cách sử dụng CAST đơn giản (MyString AS BINARY16), mà không cần tất cả các chuỗi khó hiểu của CONCAT + SUBSTR.

Các trường BINary (16) được so sánh / sắp xếp / lập chỉ mục nhanh hơn nhiều so với các chuỗi và cũng chiếm ít hơn hai lần không gian trong cơ sở dữ liệu


2
Chạy truy vấn này cho thấy CAST chuyển đổi chuỗi uuid thành byte ASCII: set @a = uuid (); chọn @a, hex (diễn viên (@a NHƯ BINary (16))); Tôi nhận được 16f20d98-9760-11e4-b981-feb7b39d48d6: 3136663230643938 2D 39373630 2D 3131 (thêm khoảng trắng để định dạng). 0x31 = ascii 1, 0x36 = ascii 6. Chúng tôi thậm chí còn nhận được 0x2D, ​​đó là dấu gạch nối. Điều này không khác nhiều so với việc chỉ lưu trữ hướng dẫn dưới dạng chuỗi, ngoại trừ việc bạn cắt ngắn chuỗi ở ký tự thứ 16, loại bỏ phần ID cụ thể của máy.
bigh_29

Vâng, đây chỉ đơn giản là cắt ngắn. select CAST("hello world, this is as long as uiid" AS BINARY(16));sản xuấthello world, thi
MD004
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.