我是否使用varchar(36)或有更好的方法吗?
当我问到为我的对象存储GUID的最佳方法时,我的DBA问我,为什么我需要存储16个字节,而我可以用4个字节用Integer做同样的事情.既然他把那个挑战放在我身边,我想现在是提及它的好时机.话虽如此...
如果要最佳地利用存储空间,可以将guid存储为CHAR(16)二进制文件.
我会将它存储为char(36).
添加到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
char(36)将是一个不错的选择.此外,还可以使用MySQL的UUID()函数,该函数返回36个字符的文本格式(带连字符的十六进制),可用于从db中检索此类ID.
"更好"取决于你所优化的内容.
您对存储大小/性能与易于开发的关注程度有多大?更重要的是 - 你生成足够的GUID,或者经常提取它们,重要吗?
如果答案是"否",char(36)
那就足够了,它使存储/获取GUID变得简单.否则,这binary(16)
是合理的,但你必须依靠MySQL和/或你选择的编程语言来转换来自通常的字符串表示.
二进制(16)会好,比使用varchar(32)更好.
应调整KCD发布的GuidToBinary例程,以考虑GUID字符串中时间戳的位布局.如果字符串表示版本1 UUID,就像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,因为big endian已经产生了最佳的二进制索引字节顺序.但是,您确实希望在较低字节前面移动最重要的字节.因此,EFG先行,然后是中间位和低位.在一分钟内使用uuid()生成十几个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个半字节中变化.这些是时间戳的最低有效位,这意味着当我们将其转换为可索引的字节数组时,我们希望将它们推到右侧.作为一个反例,最后一个ID是最新的,但KCD的交换算法会将它放在第3个ID之前(直流前3e,第一个块的最后一个字节).
索引的正确顺序是:
1e497556eec5eb6... 1e497556eec5f10... 1e497556eec8ddc... 1e497556eee30d0... 1e497556efda038... 1e497556f9641bf... 1e49755758c3e3e...
有关支持信息,请参阅此文章:http://mysql.rjweb.org/doc.php/uuid
***请注意,我没有从时间戳的高12位分割版本半字节.这是你的例子中的D半字节.我把它扔在前面.所以我的二进制序列最终成为DEFG9ABC等等.这意味着我所有索引的UUID都以相同的半字节开头.文章做了同样的事情.
对于那些刚刚陷入困境的人来说,根据Percona的研究,现在有了更好的选择.
它包括重新组织UUID块以获得最佳索引,然后转换为二进制以减少存储.
阅读完整的文章在这里