mysql推荐自增主键,是因为innodb聚簇索引要求物理顺序存储,自增id保证新行始终追加到b+树最右页末尾,避免页分裂、控制碎片、维持高效顺序io;而uuid等随机主键无论优化存储格式(如binary(16)或uuid_to_bin)均无法消除插入位置随机性,仍导致高频页分裂、页利用率低(

MySQL推荐自增主键,不是因为“看起来整齐”,而是InnoDB聚簇索引的物理存储机制决定了:只有顺序追加才能避免页分裂、控制碎片、维持IO效率——这不是调优能绕开的硬约束。
自增ID如何避免页分裂
页分裂只在“必须往已满页中间插入”时触发,而自增ID保证新行总落在B+树最右叶子页末尾。页满就分配新页,旧页不动,不搬数据、不更新父节点指针。
-
SHOW TABLE STATUS LIKE 't'中Data_free持续 > 表总大小5%(比如 >1GB),基本说明分裂严重 - 实测百万级写入后,自增ID表的页利用率通常 >80%,UUID表常低于50%
-
innodb_autoinc_lock_mode=1(默认)下,单条INSERT只持轻量 mutex,不会阻塞其他事务
UUID做主键为什么改用BINARY(16)也救不了
把 CHAR(36) 改成 BINARY(16) 或用 UUID_TO_BIN(uuid, TRUE),只是压缩存储,不改变插入位置的随机性。
- 原生UUID字节序是时间戳+随机段混排,高位不单调 → B+树仍需二分查找,90%以上插入落在中间页
-
UUID_TO_BIN(uuid, TRUE)(MySQL 8.0+)把时间戳前置,仅降低约15%分裂频率,相比自增ID仍差一个数量级 - 二级索引叶子节点存主键值:
BINARY(16)比BIGINT多占8字节 → 所有二级索引体积翻倍
业务ID“看似有序”为何照样分裂
很多团队用 CONCAT(DATE_FORMAT(NOW(),'%Y%m%d'), LPAD(id,6,'0')) 这类格式,误以为能规避问题,实际不行。
- 不同时间生成的ID可能交叉回插:比如先插
20260501000001和20260502000001,再插20260501000002,就必须回到第一天的页里 - 分库分表后各子表用本地
AUTO_INCREMENT,全局ID变成1,2,3…1,2,3…,物理插入位置完全跳跃 -
SELECT MAX(id)+1手动算ID:并发下要么冲突报错,要么跳号,且无法阻止后续事务往空缺位置回填
真正容易被忽略的代价
页分裂带来的不是“慢一点”,而是缓存局部性崩坏、碎片持续累积、二级索引膨胀、范围查询退化——这些影响会随数据增长指数级放大,且 OPTIMIZE TABLE 只能临时缓解,无法根治。
如果必须用分布式ID(如雪花算法),关键不是“全局唯一”,而是确保时间戳高位+机器ID低位组合后整体趋势递增;否则,和UUID无异。











