将UUID作为二进制(16)插入和选择

62

我不明白为什么

SELECT UUID();

返回类似以下内容:

3f06af63-a93c-11e4-9797-00505690773f

但是,如果我将它插入到一个二进制(16)字段中(使用UUID()函数),例如在BEFORE INSERT触发器中运行SELECT语句,则返回类似以下内容:

但如果我将其插入到二进制(16)字段中(使用UUID()函数),例如通过BEFORE INSERT触发器,并运行SELECT,它会返回类似以下的东西:

0782ef48-a439-11

注意,这两个UUID不是相同的数据。

我知道二进制和UUID字符串看起来不一样,但是选择的数据至少应该一样长吧?否则它怎么可能同样有可能是唯一的?

把它存储为char(36)是否更好?我只需要它是唯一的,以防止重复插入。它从未被选中或用于连接。

编辑:

触发器之前会像这样:

BEGIN

if NEW.UUID IS NULL THEN

NEW.UUID = UUID();

END IF

END

2
BINARY(16) 只能容纳 16 个字符。因此,它将包含您存储在其中的 UUID 的前 16 个字符。 - Barmar
根据https://dev59.com/hmgu5IYBdhLWcg3w9br_,情况并非如此。另请参阅https://dev59.com/e3TYa4cB1Zd3GeqPw55H - nickdnk
该答案使用UNHEX()函数将UUID转换为适合16字节的数字。 - Barmar
2
十六进制数字 0F 分别对应于十进制中的数字 015。每个十六进制数字对应 4 个位,因此 2 个十六进制数字相当于 8 个位,即 1 字节。 - Barmar
1
那么为了正确保存UUID,我应该选择:A. 将其转换为十六进制以将其压缩为16个字节,或者B. 将其存储为char(36)? - nickdnk
显示剩余10条评论
6个回答

135
所以,作为对评论的回应。将36个字符的UUID存储为二进制(16)的正确方法是以以下方式进行插入操作:
INSERT INTO sometable (UUID) VALUES
       (UNHEX(REPLACE("3f06af63-a93c-11e4-9797-00505690773f", "-","")))

因为 UUID 已经是一个十六进制的值,所以我们使用 UNHEX 函数。我们在语句中使用 REPLACE 来去除连字符,将长度减少到 32 个字符(表示我们的 16 字节的十六进制值)。你可以在存储之前的任何时候执行此操作,这样就不需要由数据库处理。
你可以像这样检索 UUID:
SELECT HEX(UUID) FROM sometable;

只是以防万一有人看到这个帖子并不确定这是如何工作的。
还记得:如果你使用UUID选择一行,在条件中使用UNHEX()
SELECT * FROM sometable WHERE UUID = UNHEX('3f06af63a93c11e4979700505690773f');

或者文字注释(正如Alexis Wilke所述):

SELECT * FROM sometable WHERE UUID = 0x3f06af63a93c11e4979700505690773f;

而不是在列上使用HEX()

SELECT * FROM sometable WHERE HEX(UUID) = '3f06af63a93c11e4979700505690773f';

最后一种解决方案虽然有效,但需要MySQL在确定哪些行匹配之前将所有UUID转换为HEX格式。这种方法非常低效。
编辑:如果您使用的是MySQL 8,建议查看SlyDave回答中提到的UUID函数。本答案仍然正确,但它没有对UUID索引进行优化,而这可以通过使用这些函数来实现原生支持。如果您使用的是MySQL 8以下版本或MariaDB,您可以实施Devon的polyfill,在MySQL的旧版本上提供相同的功能。

很好的问题和答案。一个与主题无关的问题是,当客户端 UI 后来检索并更新记录时,这个 UUID 是关键。在稍后提交回来时,我应该在客户端记住二进制还是字符串? - Jeb50
@Jeb50 客户端(以及您的API)应将UUID处理为带有或不带有破折号的字符串。 - nickdnk

64

从MySQL 8开始,你可以使用两个新的UUID函数

  • BIN_TO_UUID

SELECT BIN_TO_UUID(uuid, true) AS uuid FROM foo;
-- 3f06af63-a93c-11e4-9797-00505690773f
  • UUID_TO_BIN

    UUID_TO_BIN函数将UUID值转换为二进制字符串。
    INSERT INTO foo (uuid) VALUES (UUID_TO_BIN('3f06af63-a93c-11e4-9797-00505690773f', true));
    

    这种方法还支持重新排列uuid的时间组件以增强索引性能(按时间顺序排序),只需将第二个参数设置为true即可 - 这仅适用于UUID1。

    如果您正在使用trueUUID_TO_BIN标志上提高索引性能(建议),您还必须在BIN_TO_UUID上设置它,否则它无法正确地转换回来。

    请参阅文档了解更多详细信息。


  • 2
    经过大量的研究,我们决定从MySQL迁移到PostgreSQL,因为它们有UUID数据类型(就像自动增量一样,它会自动插入,更重要的是,您可以直接使用字符串读取它)。否则,我们必须在使用MySQL 5.7或8的ID进行更新或读取的任何地方重新构建我们的整个代码库。 - friek108
    MySQL也有UUID()函数,专门用于此目的。虽然它不是一种数据类型,但它相当于binary(16),并生成可插入友好的顺序(非随机)UUID。 - nickdnk
    感谢@nickdnk - 但最终这意味着我们必须更新我们整个代码库中的每一个查询以包含此函数。 - friek108
    或者您可以使用触发器,@friek108 :) - nickdnk
    4
    你如何使得从MySQL迁移到PostgreSQL比更改代码库以插入UUID更容易?我真的不明白这该怎么做。你可以详细说明一下吗? - nickdnk

    22
    MySQL 5或MariaDB的BIN_TO_UUID和UUID_TO_BIN的Polyfill,带有swap_flag参数。
    DELIMITER $$
    
    CREATE FUNCTION BIN_TO_UUID(b BINARY(16), f BOOLEAN)
    RETURNS CHAR(36)
    DETERMINISTIC
    BEGIN
       DECLARE hexStr CHAR(32);
       SET hexStr = HEX(b);
       RETURN LOWER(CONCAT(
            IF(f,SUBSTR(hexStr, 9, 8),SUBSTR(hexStr, 1, 8)), '-',
            IF(f,SUBSTR(hexStr, 5, 4),SUBSTR(hexStr, 9, 4)), '-',
            IF(f,SUBSTR(hexStr, 1, 4),SUBSTR(hexStr, 13, 4)), '-',
            SUBSTR(hexStr, 17, 4), '-',
            SUBSTR(hexStr, 21)
        ));
    END$$
    
    
    CREATE FUNCTION UUID_TO_BIN(uuid CHAR(36), f BOOLEAN)
    RETURNS BINARY(16)
    DETERMINISTIC
    BEGIN
      RETURN UNHEX(CONCAT(
      IF(f,SUBSTRING(uuid, 15, 4),SUBSTRING(uuid, 1, 8)),
      SUBSTRING(uuid, 10, 4),
      IF(f,SUBSTRING(uuid, 1, 8),SUBSTRING(uuid, 15, 4)),
      SUBSTRING(uuid, 20, 4),
      SUBSTRING(uuid, 25))
      );
    END$$
    
    DELIMITER ;
    
    --
    -- Tests to demonstrate that it works correctly. These are the values taken from
    -- https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_uuid-to-bin
    --
    -- If you run these SELECTs using the above functions, the 
    -- output of the two columns should be exactly identical in all four cases.
    SET @uuid = '6ccd780c-baba-1026-9564-5b8c656024db';
    SELECT HEX(UUID_TO_BIN(@uuid, 0)), '6CCD780CBABA102695645B8C656024DB';
    SELECT HEX(UUID_TO_BIN(@uuid, 1)), '1026BABA6CCD780C95645B8C656024DB';
    SELECT BIN_TO_UUID(UUID_TO_BIN(@uuid,0),0), '6ccd780c-baba-1026-9564-5b8c656024db';
    SELECT BIN_TO_UUID(UUID_TO_BIN(@uuid,1),1), '6ccd780c-baba-1026-9564-5b8c656024db';
    

    包括了从https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_uuid-to-bin中选择的示例,这些示例证明了上述代码返回与8.0函数完全相同的结果。这些函数被认为是确定性的,因为它们对于给定的输入总是产生相同的输出。请参阅https://dev.mysql.com/doc/refman/8.0/en/create-procedure.html

    正是我所需要的。谢谢! - James Marks
    1
    你救了我的一天!由于我的主机只支持MySQL 5,我本来要被迫重写所有的BIN_TO_UUID调用! - Michael Wallace
    你应该在两个函数中都添加SWAP_FLAG,因为这是本地函数支持的内容:https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_uuid-to-bin和https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_bin-to-uuid。 - nickdnk
    1
    @Devon,我已经弄清楚并编辑了你的答案。感谢你指导我正确的方向。 - nickdnk
    我尝试在我的服务器上使用这个,但是当我使用UUID_TO_BIN(uuid(),1)时,它会给我一些奇怪的字符,比如��6Z5�*,�U��)..。为什么会出现这种情况? - Muhammad Fauzi
    1
    @MuhammadFauzi 它产生一个二进制输出。如果你想显示UUID,你需要执行HEX(UUID_TO_BIN(uuid(),1)),或者如果你已经将其放入二进制列中,则选择HEX(col)。 - nickdnk

    16

    我使用的是MariaDB,因此没有BIN_TO_UUID函数系列。不过我还是成功获取了相应的值。

    bin -> hex

    这里,uuid是UUID的二进制(16)值;您将使用下面的值来选择其可读版本。

    LOWER(CONCAT(
        SUBSTR(HEX(uuid), 1, 8), '-',
        SUBSTR(HEX(uuid), 9, 4), '-',
        SUBSTR(HEX(uuid), 13, 4), '-',
        SUBSTR(HEX(uuid), 17, 4), '-',
        SUBSTR(HEX(uuid), 21)
    ))
    

    十六进制 -> 二进制

    这里,cc6e6d97-5501-11e7-b2cb-ceedca613421 是 UUID 的可读版本,您将在 WHERE 子句中使用下面的值寻找它。

    UNHEX(REPLACE('cc6e6d97-5501-11e7-b2cb-ceedca613421', '-', ''))
    

    干杯


    为什么不像我的答案一样直接替换破折号呢?做5个子字符串调用有什么优势? - nickdnk
    3
    你说得完全正确。我也不知道,我想可能是要转向把手的位置吧。谢谢 :) - Alain Tiemblo
    好的。我也会在应用层做字符串连接,而不是让数据库每个UUID都执行五次HEX()和SUBSTR()函数...因为PHP、C#、Java或其他语言可以轻松地通过拆分UUID来插入破折号,所以没有必要让数据库去做这些操作。实际上,在我的应用程序中,我根本不使用破折号,因为它们多余了。 - nickdnk

    16
    其他答案是正确的。 UUID() 函数返回一个36个字符的字符串,需要使用所示函数(UNHEX() 或在较新的平台上使用 UUID_TO_BIN())进行转换。
    然而,如果你使用自己的软件创建 UUIDs,则可以使用 十六进制字面量 符号 代替。
    因此,我将在 MySQL 的 UUID() 函数中使用以下内容:
    INSERT INTO sometable (id) VALUES (UNHEX(REPLACE(UUID(), '-', '')));  -- all versions
    INSERT INTO sometable (id) VALUES (UUID_TO_BIN(UUID());               -- since v8.0
    

    但在我生成自己的UUID时,请使用此选项;

    INSERT INTO sometable (id) VALUES 0x3f06af63a93c11e4979700505690773f;
    

    同样地,你可以在WHERE从句中使用十六进制文字:

    SELECT * FROM sometable WHERE id = 0x3f06af63a93c11e4979700505690773f;
    

    如果您不必每次将数据转换为UUID字符串,这将更快。

    注意:'0xaBc中的'x'是区分大小写的。 然而,十六进制数字并不区分大小写。


    来这里寻找字面量的解决方案...谢谢! - Robert Penridge
    如果您正在使用字符集不是UTF-8的DB以及版本大于等于2.2.4的MariaDB Java连接器,那么您的插入语句应该像这样:INSERT INTO sometable (id) VALUES (UNHEX(REPLACE(CONVERT(UUID() using utf8mb4), '-', ''))) 以确保唯一性。 - Davi Cavalcanti
    1
    @DaviCavalcanti 我想知道为什么你需要“utf8”转换。UUID只由ASCII字符组成。 - Alexis Wilke

    -2
    在MySQL 4.0及以上版本中,您可以像使用MID一样更改UUID的大小。
    SELECT MID(UUID(),1,32); # 32 characters long UUID
    SELECT MID(UUID(),1,11); # 11 characters long UUID
    

    正如@nickdnk所指出的,您不应该这样做。UUID的总长度使它们唯一。剥离其中一部分可能会导致非唯一值。

    你不应该改变UUID的大小。它被设计为唯一的,不同的部分/节由不同的来源生成。像这样取UUID的子集是在寻求麻烦。 - nickdnk
    @nickdnk,是的,你说得对。我正在更新我的答案。 - Endre Soo

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接