我花了相当长的时间寻找解决方案,并提出了以下解决方案 使用标准 MySQL 生成随机 UUID(即 UUIDv4)的 mysql 函数 功能。我正在回答我自己的问题来分享,希望它会 有用。
-- Change delimiter so that the function body doesn't end the function declaration
DELIMITER //
CREATE FUNCTION uuid_v4()
RETURNS CHAR(36) NO SQL
BEGIN
-- Generate 8 2-byte strings that we will combine into a UUIDv4
SET @h1 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET @h2 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET @h3 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET @h6 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET @h7 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
SET @h8 = LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0');
-- 4th section will start with a 4 indicating the version
SET @h4 = CONCAT('4', LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'));
-- 5th section first half-byte can only be 8, 9 A or B
SET @h5 = CONCAT(HEX(FLOOR(RAND() * 4 + 8)),
LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'));
-- Build the complete UUID
RETURN LOWER(CONCAT(
@h1, @h2, '-', @h3, '-', @h4, '-', @h5, '-', @h6, @h7, @h8
));
END
//
-- Switch back the delimiter
DELIMITER ;
注意:使用的伪随机数生成(MySQL 的
RAND
)不是
加密安全,因此存在一些可能增加冲突的偏差
风险。
RAND()
功能:
RAND() 并不意味着是一个完美的随机生成器。这是一种按需生成随机数的快速方法,可在相同 MySQL 版本的平台之间移植。
实际上,这意味着使用此函数生成的
UUID
可能(并且将会)有偏差,并且碰撞可能比预期更频繁地发生。
random_bytes()
函数在 MySQL 端生成安全的 UUID V4:
此函数返回使用 SSL 库的随机数生成器生成的 len 随机字节的二进制字符串。
因此我们可以将函数更新为:
CREATE FUNCTION uuid_v4s()
RETURNS CHAR(36)
BEGIN
-- 1th and 2nd block are made of 6 random bytes
SET @h1 = HEX(RANDOM_BYTES(4));
SET @h2 = HEX(RANDOM_BYTES(2));
-- 3th block will start with a 4 indicating the version, remaining is random
SET @h3 = SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3);
-- 4th block first nibble can only be 8, 9 A or B, remaining is random
SET @h4 = CONCAT(HEX(FLOOR(ASCII(RANDOM_BYTES(1)) / 64)+8),
SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3));
-- 5th block is made of 6 random bytes
SET @h5 = HEX(RANDOM_BYTES(6));
-- Build the complete UUID
RETURN LOWER(CONCAT(
@h1, '-', @h2, '-4', @h3, '-', @h4, '-', @h5
));
END
这应该生成足够随机的 UUID V4,无需关心冲突。
注意:不幸的是,MariaDB不支持
RANDOM_BYTES()
(参见https://mariadb.com/kb/en/function-differences- Between-mariadb-105-and-mysql-80/#miscellaneous)
我创建了以下测试场景:插入随机 UUID v4 作为表的主键,直到创建 40.000.000 行。发现碰撞时,该行将更新,递增
collisions
列:
INSERT INTO test (uuid) VALUES (uuid_v4()) ON DUPLICATE KEY UPDATE collisions=collisions+1;
每个函数 4000 万行后的碰撞总和为:
+----------+----------------+
| RAND() | RANDOM_BYTES() |
+----------+----------------+
| 55 | 0 |
+----------+----------------+
随着行数的增加,这两种情况下的冲突数量都会增加。
使用
RANDOM_BYTES
改编 Elias Soares 的答案,而不创建数据库函数:
SELECT LOWER(CONCAT(
HEX(RANDOM_BYTES(4)), '-',
HEX(RANDOM_BYTES(2)), '-4',
SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3), '-',
CONCAT(HEX(FLOOR(ASCII(RANDOM_BYTES(1)) / 64)+8),SUBSTR(HEX(RANDOM_BYTES(2)), 2, 3)), '-',
HEX(RANDOM_BYTES(6))
))
万一您正在使用数据库并且没有创建函数的权限,这里有与上面相同的版本,它的作用就像 SQL 表达式一样:
SELECT LOWER(CONCAT(
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'),
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), '-',
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'), '-',
'4',
LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'), '-',
HEX(FLOOR(RAND() * 4 + 8)),
LPAD(HEX(FLOOR(RAND() * 0x0fff)), 3, '0'), '-',
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'),
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0'),
LPAD(HEX(FLOOR(RAND() * 0xffff)), 4, '0')));