Skip to content

MySQL - UUID

MySQL 中的 UUID() 函数根据 RFC 4122(版本 1)生成通用唯一标识符(UUID)。这些值旨在跨所有服务器和所有时间都是唯一的。UUID 是通过当前时间戳、服务器唯一的 MAC 地址和随机数的组合生成的,这使得冲突的可能性极小。

UUID() 函数返回一个 128 位数字,格式为 UTF-8 字符串,由五个十六进制段组成,并用连字符分隔。

标准格式为:aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee。

执行该函数很简单:

SELECT UUID();

每次调用都会生成一个新且唯一的值。

UUID()
5c6f7b1e-7517-11ee-8a39-0242ac130003

在分布式系统中使用 UUID 作为主键很常见,但也有一些权衡:

  • 优点:
    • 全球唯一性: 可以在不同的系统上生成记录而不会产生 ID 冲突,这对于微服务或离线数据同步非常理想。
    • 安全性: 它们不暴露顺序信息。用户无法从 /users/123 猜测出 /users/124。
    • 无状态生成: 应用程序可以生成自己的 ID,而无需查询数据库。
  • 缺点:
    • 存储大小: UUID 字符串(VARCHAR(36))占用 36 字节,远超 4 字节的 INT 或 8 字节的 BIGINT。
    • 性能: UUID 不是顺序的。将新的随机值插入索引(尤其是 InnoDB 主键这样的聚簇索引)会导致页分裂和索引碎片化,从而导致写入和读取变慢。
    • 可读性: 它们很难供人类阅读、记忆或比较。

最佳实践:将 UUID 存储为 BINARY(16)

Section titled “最佳实践:将 UUID 存储为 BINARY(16)”

为了缓解存储和性能问题,现代最佳实践是将 UUID 存储在 BINARY(16) 列中。这直接存储 128 位值,只使用 16 字节。MySQL 8.0+ 提供了便捷的函数:UUID_TO_BIN() 用于将 UUID 字符串转换为二进制,BIN_TO_UUID() 用于将其转换回可显示的形式。

在这里,我们创建一个 Logins 表,其中 id 是 BINARY(16) 主键。

CREATE TABLE Logins (
id BINARY(16) PRIMARY KEY,
user_id INT NOT NULL,
login_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45)
);

插入时,您必须将 UUID 字符串转换为二进制。选择时,将其转换回可读字符串。

-- 生成新的 UUID 并存储在变量中以提高清晰度
SET @new_uuid = UUID();
-- 使用 UUID_TO_BIN() 插入记录
INSERT INTO Logins(id, user_id, ip_address)
VALUES (UUID_TO_BIN(@new_uuid), 101, '192.168.1.100');
-- 使用 BIN_TO_UUID() 检索记录
SELECT BIN_TO_UUID(id) as login_id, user_id, login_time
FROM Logins
WHERE user_id = 101;

输出显示了人类可读的 UUID:

login_iduser_idlogin_time
your-generated-uuid-here1012023-10-27 10:30:00

已弃用的实践:将 UUID 存储为 VARCHAR

Section titled “已弃用的实践:将 UUID 存储为 VARCHAR”

尽管简单,但将 UUID 存储在 VARCHAR(36) 列中效率极低。它使用的存储空间是两倍多,并且索引性能不佳。在新应用程序中应避免使用。

-- 反模式:低效存储
CREATE TABLE OldOrders (
order_id VARCHAR(36) PRIMARY KEY,
product_name VARCHAR(100)
);
-- 插入是直接的,但性能会受影响
INSERT INTO OldOrders (order_id, product_name)
VALUES (UUID(), 'Legacy Keyboard');

建议: 在现代 MySQL 数据库中,始终优先使用 BINARY(16) 来存储 UUID。对于需要兼顾性能和 UUID 优势的应用程序,可以考虑在应用程序层面实现时间排序的 UUID(例如 UUIDv7),以改善索引局部性。