我应该如何在MySQL表中存储GUID?


146

我使用varchar(36)还是有更好的方法呢?


1
“ thaBadDawg”提供了一个很好的答案。Stack Overflow上有一个讨论该主题的并行线程。我向该线程回答添加了一些注释,这些注释更详细地链接到资源。这里是问题链接:stackoverflow.com/questions/547118/storing-mysql-guid-uuids-我希望当人们开始考虑AWS和Aurora时,这个主题会变得越来越普遍。
Zack Jannsen

Answers:


104

当我问到为对象存储GUID的最佳方法时,我的DBA问我为什么当我可以用整数将4字节的内容做同样的事情时,为什么需要存储16字节的内容。自从他向我提出挑战以来,我认为现在是提起挑战的好时机。话虽如此...

如果要最大程度地利用存储空间,可以将GUId存储为CHAR(16)二进制文件。


176
因为只有16个字节,所以您可以在不同的时间,不同的机器上,在不同的数据库中生成事物,并且仍然可以无缝地将数据合并在一起:)
Billy ONeal 2010年

4
需要回复,什么是char 16二进制?不是字符?不是二进制的?我没有在任何mysql gui工具中看到该类型,也没有在mysql站点中看到任何文档。@BillyONeal
nawfal 2012年

3
@nawfal:Char是数据类型。BINARY是针对类型的类型说明符。它唯一的作用就是修改MySQL的排序规则。有关更多详细信息,请参见dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html。当然,如果数据库编辑工具允许您直接使用BINARY类型,则可以直接使用BINARY类型。(较早的工具不知道二进制数据类型,但是知道二进制列标志)
Billy ONeal

2
CHAR和BINARY字段本质上是相同的。如果您想将其带到最基本的级别,则CHAR是一个二进制字段,期望值为0到255,目的是用从查找表映射的值表示该值(现在大多数情况下为UTF8)。BINARY字段期望相同类型的值,而无意表示来自查找表的所述数据。我在4.x天内使用CHAR(16),因为那时MySQL并不像现在那样好。
thaBadDawg 2012年

15
有几个很好的理由说明GUID远胜于自动增量。Jeff Atwood列出了这些。对我来说,使用GUID的最大好处是我的应用程序不需要数据库往返即可知道实体的键:我可以以编程方式填充它,而在使用自动递增字段时则无法做到。这使我免于烦恼:使用GUID,我可以用相同的方式管理实体,而不管实体已经被保留还是全新。
Arialdo Martini

48

我会将其存储为char(36)。


5
我看不到为什么要存储-
Afshin Mehrabani

2
@AfshinMehrabani简单,直接,易于阅读。当然,这不是必需的,但是如果存储这些多余的字节没有影响,那么这是最好的解决方案。
user1717828

2
存储破折号可能不是一个好主意,因为它将导致更多的开销。如果要使其易于阅读,请使应用程序带有破折号。
卢卡·费里

@AfshinMehrabani的另一个考虑因素是从数据库中解析它。大多数实现将在有效的GUID中使用破折号。
瑞安·盖茨

您可以在提取时插入连字符,以轻松将char(32)转换为char(36)。使用mySql的Insert FN。
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,没有从字符串中删除连字符:CREATE FUNCTION GuidToBinary($ guid char(36))RETURNS binary(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)));
乔纳森·奥利弗

4
出于好奇,这些函数优于仅UNHEX(REPLACE(UUID(),'-','')),因为它按可在聚集索引中更好地执行的顺序排列位。
Slashterix

这是非常有用的,但我觉得它可能与源加以改进CHARBINARY等价(该文档似乎暗示有重要的区别和原因聚集索引的性能的解释是重新排序字节更好。
帕特里克·中号

当我使用它时,我的GUID改变了。我尝试使用unhex(replace(string,'-',``))和上面的函数插入它,当我使用相同的方法将它们转换回时,所选的guid不是插入的。什么改变了GUID?我所做的只是从上面复制了代码。
vsdev 2015年

@JonathanOliver能否请您分享BinaryToGuid()函数的代码?
阿伦·阿瓦纳森

27

char(36)将是一个不错的选择。还可以使用MySQL的UUID()函数,该函数返回36个字符的文本格式(带连字符的十六进制),该格式可用于从数据库中检索此类ID。


19

“更好”取决于您要优化的内容。

您在乎存储大小/性能与易于开发之间有多少关系?更重要的是-您是否生成足够的GUID或足够频繁地获取它们,这很重要?

如果答案为“否”,char(36)那就足够了,这会使存储/获取GUID非常简单。否则,这binary(16)是合理的,但是您必须依靠MySQL和/或您选择的编程语言来从通常的字符串表示形式来回转换。


2
如果您托管该软件(例如,一个网页)并且不在客户端中销售/安装,则始终可以从char(36)开始,以便在软件的早期阶段轻松开发,并转变为更紧凑的版本格式随着系统使用量的增长而开始需要优化。
哈维·蒙特罗

1
更大的char(36)最大的缺点是索引将占用多少空间。如果数据库中有大量记录,则将索引的大小增加一倍。
bpeikes


7

应该对KCD发布的GuidToBinary例程进行调整,以考虑到GUID字符串中时间戳的位布局。如果该字符串表示版本1 UUID,如uuid()mysql例程返回的UUID,则时间分量将嵌入到字母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,因为大端已产生最佳的二进制索引字节顺序。但是,您确实希望将最高有效字节移到较低字节的前面。因此,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的交换算法会将其放在第三个ID之前(dc之前的3e,第一个块的最后一个字节)。

正确的索引顺序为:

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

请参阅本文以获取支持信息:http : //mysql.rjweb.org/doc.php/uuid

***请注意,我不会从时间戳的高12位中拆分版本半字节。这是您示例中的D小节。我只是把它放在前面。所以我的二进制序列最终是DEFG9ABC,依此类推。这意味着我所有索引的UUID都以相同的半字节开头。这篇文章做同样的事情。


节省存储空间的目的是什么?还是使排序有用?
MD004

1
@ MD004。它创建了更好的排序索引。空间保持不变。
bigh_17年

5

对于那些刚刚涉足此问题的人,根据Percona的研究,现在有一个更好的选择。

它包括重新组织UUID块以获得最佳索引,然后转换为二进制文件以减少存储量。

此处阅读全文


我以前读过那篇文章。我觉得这很有趣,但是如果我们要通过二进制ID进行过滤,应该如何执行查询?我想我们需要再次十六进制,然后再应用标准。这么苛刻吗?为什么要存储binary(16)(确保比varchar(36)好)而不是存储8个字节的bigint?
Maximus Decimus

2
有来自MariaDB的更新的文章应该回答你的问题mariadb.com/kb/en/mariadb/guiduuid-performance
sleepycal

首先,UUIDv4是完全随机的,不需要分块。
Mahmoud Al-Qudsi

2

我建议使用以下功能,因为@ bigh_29提到的功能会将我的向导转换为新的向导(出于我不了解的原因)。而且,在我对表进行的测试中,这些速度要快一些。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,则可以使用简单的CAST(MyString AS BINARY16)将其存储为BINARY(16),而无需担心CONCAT + SUBSTR的所有麻烦。

与字符串相比,对BINARY(16)字段进行比较/排序/索引的速度要快得多,并且在数据库中占用的空间也要少两倍


2
运行此查询显示CAST将uuid字符串转换为ASCII字节:set @a = uuid(); 选择@a,hex(cast(@a AS BINARY(16))); 我得到16f20d98-9760-11e4-b981-feb7b39d48d6:3136663230643938 2D 39373630 2D 3131(添加了用于格式化的空间)。0x31 = ascii 1,0x36 = ascii6。我们甚至得到0x2D,即连字符。除了将guid存储为字符串外,这没有什么不同,除了您将字符串截断为第16个字符外,这会切断ID的特定于机器的部分。
bigh_29

是的,这只是截断。select CAST("hello world, this is as long as uiid" AS BINARY(16));生产hello world, thi
MD004
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.