为什么说 AUTO_INCREMENT 自增主键通常比 UUID 更适合作为 MySQL 主键?原因在于,自增主键采用顺序写入方式,能够有效减少页分裂,提升缓存局部性,并显著降低磁盘 I/O 开销。相比之下,UUID 的随机性会让索引碎片更严重,碎片率往往高出数倍,范围查询也容易退化为随机 I/O,同时还会带来存储空间膨胀,以及因传参格式不一致导致索引失效等问题。

AUTO_INCREMENT 主键在绝大多数业务场景下都比 UUID() 更适合充当 MySQL 主键,关键并不是“UUID 唯一性不足”,而是它会直接破坏 InnoDB 聚簇索引原本按顺序组织数据的物理存储方式。
自增ID插入不易触发页分裂,而UUID几乎一定会增加页分裂概率
InnoDB 的聚簇索引会将数据行与主键值存储在一起,B+ 树叶子节点必须按照主键顺序进行组织和链接。AUTO_INCREMENT 通常只会在索引尾部顺序追加,新页分配更简单、更连续,INSERT 性能也更稳定,延迟常见在 0.2–0.5ms;
而 UUID() 生成的是随机字符串(如 '550e8400-e29b-41d4-a716-446655440000'),每次插入前都需要遍历 B+ 树寻找目标位置,大多数情况下会落到中间页——一旦已有页写满,就会触发页拆分、指针重连,导致额外 I/O 明显增加。
在百万级数据写入测试中,使用 UUID 的表其 Data_free 值(可通过 SHOW TABLE STATUS 查看)通常会达到自增主键表的 3 倍以上,这说明索引碎片非常明显。
UUID主键会让范围查询更容易退化为随机IO
像WHERE id BETWEEN 1000 AND 2000 这样的范围查询,在使用自增主键时,通常只需要扫描少量连续的数据页;
但如果改成 UUID 主键,逻辑上相近的 ID 在磁盘上的物理位置往往并不相邻,可能分散在几十甚至上百个不同的数据页中,从顺序读取变成大量随机读取,缓存命中率也会显著下降。
还有一个更容易被忽视的问题:如果 ORM 框架或应用层传参不够规范(例如传入 '9b1deb4d3b7d4bad9bdd2b0d7b3dcb6d' 时缺少横杠,或者大小写格式混杂),那么 WHERE id = ? 很可能无法正确命中索引,执行计划中的 type 甚至会从 const 退化为 ALL,直接影响查询性能。
存储空间膨胀会进一步拖累所有索引性能
MySQL 主键不仅仅影响主键索引本身,它还会被所有二级索引重复存储,作为定位行记录的“指针”使用。BIGINT 主键只占 8 字节,而 CHAR(36) 类型的 UUID 则需要 36 字节——如果单表达到 1000 万行,仅主键索引就可能额外多占约 280MB;如果再叠加 5 个二级索引,总体存储膨胀接近 1.4GB。
即便使用 BINARY(16) 配合 UUID_TO_BIN(UUID(), TRUE) 来优化 UUID 存储,仍然比 BIGINT 多出 8 字节,同时也无法从根本上解决 UUID 随机写入带来的索引离散和性能问题。
真正必须使用 UUID 的场景其实并不多,通常只有两类:一类是分库分表后又没有全局序列服务,另一类是多库数据需要合并且无法重新映射主键。至于常见的“防爬虫”“提升安全性”“更适合分布式系统”等说法,通常都可以通过映射表、业务层 ID 转换或前端脱敏等方式实现,没有必要用主键索引性能作为代价。