自增id能显著降低页分裂频率,根本原因是innodb聚簇索引必须按主键物理排序存储,而自增id保证新行总追加到最右叶子页末尾,页满时仅分配新页、无需数据迁移;若插入位置落在已满页中间(如uuid或乱序业务id),则触发分裂——搬移约一半数据、更新指针、调整b+树节点。

自增ID做主键能显著降低页分裂频率,根本原因不是“它看起来有序”,而是InnoDB的聚簇索引必须按主键物理排序存储,而顺序写入天然避开中间页插入——这才是页分裂的触发开关。
页分裂到底在什么时刻发生
页分裂不是“页满了就分裂”,而是“要在已满或快满的页中插入新记录,且该记录逻辑位置落在页内”时才触发。InnoDB数据页默认16KB,填满后若还要往中间插一条,就必须拆分:搬走约一半行到新页、更新父节点指针、可能连带调整上层B+树节点。
- 自增ID插入:总是追加到当前最右叶子页末尾 → 页满即分配新页,旧页不动,无数据迁移
- UUID或业务编码插入:B+树需二分查找定位插入点 → 极大概率落到中间某页 → 一旦该页利用率>90%,立刻分裂
- 观察证据:执行
SHOW TABLE STATUS LIKE 't',持续看到Data_free值远高于表总大小5%(如 >1GB),基本可判定分裂严重
为什么“看起来有序”的业务主键照样分裂
很多团队误以为用 CONCAT(DATE_FORMAT(NOW(),'%Y%m%d'), LPAD(id,6,'0')) 这类格式就能规避问题,实际不行——id 段不保证全局递增,不同时间点生成的ID仍会交叉回插。
- 例如:20260501000001 和 20260502000001 插入后,又来个 20260501000002,它必须插回第一天的页里
- 分库分表后各子表用本地
AUTO_INCREMENT,导致全局ID为 1,2,3…1,2,3…,物理插入位置完全跳跃 -
SELECT MAX(id)+1手动算ID:并发下要么冲突报错,要么跳号,且无法阻止后续事务往空缺位置回填
自增ID不是万能,但它的“可控性”是关键
自增ID允许你把不可控的随机性,收束成可预测的追加行为。即使出现断号(事务回滚、批量插入预占、MySQL 8.0重启后计数器不回退),也不影响插入局部性。
-
innodb_autoinc_lock_mode=1(默认)下,单行INSERT只持轻量mutex,不阻塞其他事务 - 用
BIGINT UNSIGNED而非INT,避免42亿上限过早触达(尤其日均写入超百万的表) - 绝对不要
ALTER TABLE t AUTO_INCREMENT = N来“修正”断号——它只影响下一次分配,对已有页布局零作用 - 如果必须用分布式ID(如雪花算法),确保时间戳高位+机器ID低位组合后整体趋势递增,而非纯随机哈希
真正容易被忽略的是:页分裂带来的不只是写慢,它让 Data_free 持续升高,缓冲池缓存大量低利用率页,最终使 SELECT * FROM t WHERE id > ? ORDER BY id LIMIT 20 这种常见分页查询,从顺序读变成随机IO风暴。











