自增int主键比varchar(36)uuid快得多,因自增id顺序插入减少页分裂、提升缓存命中率,而uuid随机插入导致频繁页分裂、随机i/o和索引碎片;insert延迟从0.2–0.5ms升至1.5–4ms,order by id退化为全表扫描,单页存储量下降约60%。

为什么自增 INT 主键比 VARCHAR(36) UUID 快得多
因为 B+ 树索引的页分裂和插入局部性完全不同。自增整数按顺序写入,新记录总在索引末尾追加,页分裂少、缓存友好;UUID 是随机字符串,插入位置完全不可预测,导致频繁页分裂、大量随机 I/O 和索引碎片。
INSERT 性能差距在真实业务中有多明显
在每秒写入 500+ 行的订单表中,用 BIGINT 自增主键时 INSERT 延迟稳定在 0.2–0.5ms;换成 VARCHAR(36) UUID 后,延迟跳到 1.5–4ms,高峰期甚至触发 InnoDB 行锁等待。这不是理论值,是慢日志里能直接捞出来的 Query_time。
- UUID 每次插入都要搜索整个 B+ 树定位插入点,而自增 ID 只需定位到最后一页
- InnoDB 的聚簇索引直接按主键排序存储数据行,UUID 导致物理存储乱序,
SELECT ... ORDER BY id变成全表扫描级开销 -
innodb_page_size=16K下,一个页存约 100 条自增记录,但可能只存 30–40 条 UUID 记录(因字符串长度和填充碎片)
想用 UUID 真就完全不行?这几个折中方案得知道
如果业务强依赖分布式生成 ID(比如分库分表前就定死了 UUID),至少别用原生 UUID() 函数——它返回的是标准格式带横杠的字符串,多占 4 字节且无法走索引优化。
- 用
REPLACE(UUID(), '-', '')去掉横杠,存为CHAR(32),节省空间且比较快一点 - 更推荐
UUID_TO_BIN(UUID(), TRUE)(MySQL 8.0+),把 UUID 转成 16 字节二进制并做字节重排,让时间戳部分靠前,提升插入局部性 - 绝对不要把 UUID 当作
PRIMARY KEY还同时建id BIGINT AUTO_INCREMENT——冗余主键会加倍写放大,还容易让开发误用错字段
迁移已有 UUID 主键表时最常踩的坑
不是改个 ALTER TABLE ... MODIFY id BIGINT AUTO_INCREMENT 就完事。主键变更会锁表、重建聚簇索引,线上表千万级数据可能卡住 20 分钟以上,而且外键、索引、应用层代码全要同步改。
- 先确认所有关联表是否也用了 UUID:如果
order_items.order_id是VARCHAR(36),那改orders.id就必须连带改它,否则外键约束失败 -
SHOW CREATE TABLE查清当前主键是不是被其他索引隐式依赖(比如联合索引开头字段是主键),避免DROP PRIMARY KEY顺手干掉关键查询路径 - 应用代码里所有
WHERE id = ?都得检查参数类型——ORM 可能自动把数字转成字符串,查不到数据却不报错
实际换主键前,先在从库上跑 EXPLAIN FORMAT=TREE SELECT * FROM t WHERE id = 'xxx' 对比执行计划,看是否真从 “Index lookup” 退化成 “Table scan”。很多所谓“UUID 慢”,其实是没建对索引,或者压根没意识到主键就是聚簇索引本身。











