不能直接用char(36)存uuid作主键,因索引体积大、比较慢、i/o多;应改用binary(16),插入需unhex(replace(uuid(), '-', '')),查询也须同样转换,且需额外时间字段支持排序分页。

直接用 UUID() 替换自增ID不是简单改个类型就能跑通的,关键在于存储方式、索引效率和查询写法——不处理好反而会让查询变慢、磁盘占用翻倍、甚至主键失效。
为什么不能直接用 CHAR(36) 当主键
很多人一上来就写 id CHAR(36) PRIMARY KEY,结果发现:索引体积暴涨、B+树深度增加、JOIN 性能断崖下跌。MySQL 对字符串主键的排序和比较开销远高于二进制或整数类型。
-
CHAR(36)实际占用 36 字节(UTF8MB4 下可能更多),而BINARY(16)只占 16 字节,空间节省超 50% - 字符串比较是逐字节的,
BINARY(16)是按字节块直接比,CPU 缓存友好 - InnoDB 聚簇索引页内能放的记录数下降,导致更多磁盘 I/O
插入时必须用 UNHEX(REPLACE(UUID(), '-', ''))
MySQL 的 UUID() 返回的是带连字符的 36 字符字符串(如 '550e8400-e29b-41d4-a716-446655440000'),不能直接塞进 BINARY(16) 字段——会截断或报错 Data too long for column 'id'。
- 必须先用
REPLACE(UUID(), '-', '')去掉 4 个短横线,得到 32 位十六进制字符串 - 再用
UNHEX()把它转成 16 字节二进制值,才能写入BINARY(16) - 错误写法:
INSERT INTO users(id) VALUES (UUID())→ 直接失败 - 正确写法:
INSERT INTO users(id) VALUES (UNHEX(REPLACE(UUID(), '-', '')))
查询时也要对输入做同样转换
如果你用应用层生成的 UUID(比如 Java 的 UUID.randomUUID().toString())去查数据库,直接 WHERE id = 'xxx' 是查不到的——因为字段存的是二进制,而你传的是字符串。
- 必须把查询参数也走一遍
UNHEX(REPLACE('...', '-', '')) - 例如:
SELECT * FROM users WHERE id = UNHEX(REPLACE('550e8400e29b41d4a716446655440000', '-', '')) - 更稳妥的做法:在应用层就把 UUID 字符串转成 byte[] 或 hex string 再传给 SQL,避免每次查询都调用 MySQL 函数(影响执行计划缓存)
别忽略时间序列性丢失带来的副作用
自增 ID 天然有序,ORDER BY id DESC 就是最新数据;但 UUID()(尤其是 v4)是随机生成的,插入位置完全不可预测。
- 新记录不会聚集在索引末尾,频繁造成页分裂,写放大明显
- 如果业务强依赖“最新 N 条记录”,得额外加一个
created_at时间戳字段并建联合索引 - 某些场景下(比如分页拉取),
WHERE id > ? ORDER BY id LIMIT 20这种游标式分页会失效,必须换成基于时间的游标
真正难的不是生成 UUID,而是让它的存储、查询、排序、分页全部对齐业务真实需求——尤其当表数据量超过百万行后,BINARY(16) 和 CHAR(36) 的性能差会从毫秒级变成秒级。











