【问题标题】:How should I store GUID in MySQL tables?我应该如何在 MySQL 表中存储 GUID?
【发布时间】:2010-09-29 13:35:47
【问题描述】:

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

【问题讨论】:

  • "thaBadDawg" 提供了一个很好的答案。 Stack Overflow 上有一个讨论该主题的并行线程。我在该线程中添加了一些 cmets,以更详细地回答指向资源的链接。这是问题链接:stackoverflow.com/questions/547118/storing-mysql-guid-uuids - 我希望当人们开始考虑 AWS 和 Aurora 时,这个话题会变得更加普遍。

标签: mysql guid uuid


【解决方案1】:

当我询问为我的对象存储 GUID 的最佳方式时,我的 DBA 问我为什么我需要存储 16 个字节,而我可以用一个整数在 4 个字节中执行相同的操作。自从他向我提出了这个挑战,我认为现在是提及它的好时机。话说……

如果您想最优化地利用存储空间,您可以将 guid 存储为 CHAR(16) 二进制文件。

【讨论】:

  • 因为16字节,你可以在不同的数据库、不同的机器、不同的时间生成东西,并且仍然可以无缝地将数据合并在一起:)
  • 需要回复,char 16 二进制文件到底是什么?不是字符?不是二进制?我在任何 mysql gui 工具中都没有看到该类型,也没有在 mysql 站点中看到任何文档。 @BillyONEal
  • @nawfal:Char 是数据类型。 BINARY 是针对类型的类型说明符。它的唯一作用是修改 MySQL 进行排序的方式。有关详细信息,请参阅dev.mysql.com/doc/refman/5.0/en/charset-binary-op.html。当然,如果您的数据库编辑工具允许您这样做,您可以直接使用 BINARY 类型。 (旧工具不知道二进制数据类型,但知道二进制列标志)
  • CHAR 和 BINARY 字段本质上是相同的。如果你想把它带到最基本的级别,CHAR 是一个二进制字段,期望一个 0 到 255 的值,目的是用从查找表映射的值(现在大多数情况下,UTF8)来表示所述值。 BINARY 字段期望相同类型的值,但无意从查找表中表示所述数据。我在 4.x 时代使用 CHAR(16),因为当时 MySQL 不如现在。
  • GUID 远优于自动增量有几个很好的理由。 Jeff Atwood 列出了these one。对我来说,使用 GUID 的最大优势是我的应用程序不需要数据库往返来了解实体的键:我可以以编程方式填充它,如果我使用自动增量字段,我无法做到这一点。这让我摆脱了几个头疼的问题:使用 GUID,我可以以相同的方式管理实体,无论实体已经被持久化还是全新的实体。
【解决方案2】:

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

【讨论】:

  • 我不明白你为什么要存储-s。
  • @AfshinMehrabani 它简单、直接、可读。当然,这不是必需的,但如果存储这些额外的字节不会造成伤害,那么这是最好的解决方案。
  • 存储破折号可能不是一个好主意,因为它会导致更多开销。如果您想让它可读,请使用破折号使应用程序可读。
  • @AfshinMehrabani 另一个考虑是从数据库中解析它。大多数实现都需要一个有效的 guid 中的破折号。
  • 您可以在获取时插入连字符以轻松地将 char(32) 转换为 char(36)。使用 mySql 的 Insert FN。
【解决方案3】:

添加 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)));
  • 出于好奇,这些函数优于仅 UNHEX(REPLACE(UUID(),'-','')) 因为它按在聚集索引中表现更好的顺序排列位.
  • 这很有帮助,但我觉得可以通过 CHARBINARY 等效的源来改进它(the docs 似乎暗示存在重要差异并解释了为什么聚集索引重新排序的字节性能更好。
  • 当我使用这个时,我的 guid 发生了变化。我尝试使用 unhex(replace(string, '-', '')) 和上面的函数插入它,当我使用相同的方法将它们转换回来时,选择的 guid 不是插入的那个。什么是改变向导?我所做的只是从上面复制代码。
  • @JonathanOliver 能否分享一下 BinaryToGuid() 函数的代码?
【解决方案4】:

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

【讨论】:

    【解决方案5】:

    “更好”取决于您的优化目标。

    您在多大程度上关心存储大小/性能与开发的难易程度?更重要的是 - 您是否生成了足够多的 GUID,或者是否足够频繁地获取它们,这很重要?

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

    【讨论】:

    • 如果您托管软件(例如网页)并且不在客户端出售/安装,您可以始终以char(36)开头,以便在早期开发软件,并随着系统使用量的增长并开始需要优化而转变为更紧凑的格式。
    • 更大的 char(36) 最大的缺点是索引将占用多少空间。如果数据库中有大量记录,则索引的大小会增加一倍。
    【解决方案6】:

    Binary(16) 就好了,比使用 varchar(32) 更好。

    【讨论】:

      【解决方案7】:

      应调整 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,因为大端已经产生了最佳的二进制索引字节顺序。但是,您确实希望将最重要的字节移到较低字节的前面。因此,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。它创建了一个更好的排序索引。空间保持不变。
      【解决方案8】:

      根据 Percona 的研究,对于那些刚刚遇到这个问题的人来说,现在有一个更好的选择。

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

      阅读全文here

      【讨论】:

      • 我以前读过那篇文章。我觉得这很有趣,但是如果我们想通过二进制 ID 进行过滤,我们应该如何执行查询呢?我想我们需要再次使用十六进制然后应用标准。要求这么高吗?为什么要存储 binary(16)(肯定比 varchar(36) 好)而不是 8 字节的 bigint?
      • 有一篇来自 MariaDB 的更新文章应该可以回答您的问题 mariadb.com/kb/en/mariadb/guiduuid-performance
      • fwiw,UUIDv4 是完全随机的,不需要分块。
      【解决方案9】:

      我建议使用下面的函数,因为@bigh_29 提到的函数将我的 guid 转换为新的(出于我不明白的原因)。此外,在我在桌子上进行的测试中,这些速度要快一些。 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 ;
      

      【讨论】:

        【解决方案10】:

        如果您有一个格式化为标准 GUID 的 char/varchar 值,您可以使用简单的 CAST(MyString AS BINARY16) 将其简单地存储为 BINARY(16),而无需使用所有令人难以置信的 CONCAT + SUBSTR 序列。

        BINARY(16) 字段的比较/排序/索引比字符串快得多,并且在数据库中占用的空间也少了两倍

        【讨论】:

        • 运行此查询显示 CAST 将 uuid 字符串转换为 ASCII 字节: set @a = uuid();选择@a,十六进制(演员(@a作为二进制(16)));我得到 16f20d98-9760-11e4-b981-feb7b39d48d6 : 3136663230643938 2D 39373630 2D 3131 (为格式化添加了空格)。 0x31=ascii 1, 0x36=ascii 6。我们甚至得到 0x2D,即连字符。这与仅将 guid 存储为字符串没有太大区别,只是您在第 16 个字符处截断字符串,这会切掉机器特定的 ID 部分。
        • 是的,这只是截断。 select CAST("hello world, this is as long as uiid" AS BINARY(16)); 产生 hello world, thi
        猜你喜欢
        • 1970-01-01
        • 2010-10-19
        • 2012-07-25
        • 2012-12-17
        • 2012-11-12
        • 2020-12-31
        • 2012-05-08
        • 2010-09-22
        • 1970-01-01
        相关资源
        最近更新 更多