高频更新字段加索引必然加剧页分裂:每次update都需同步更新对应b+树叶节点,若排序位置变化则触发页内移动+页间迁移,比insert更重;删无用索引、移出联合索引前缀、mysql 8.0.23+用rebuild替代optimize table是更有效方案。

高频更新字段加索引会直接加剧页分裂
不是“可能”,而是必然:只要字段出现在任何索引中(单列或联合索引任意位置),每次 UPDATE 修改它,InnoDB 就必须同步更新对应 B+ 树的叶节点。如果新值导致排序位置变化(比如 status 从 1 → 2),行可能被挪出原页、插入到另一页,触发「页内移动 + 页间迁移」——比单纯 INSERT 更重。
常见错误现象包括:SHOW PROCESSLIST 中大量卡在 Updating 状态、innodb_row_lock_waits 持续上升、innodb_page_splits 每秒超 1 次。此时 Data_free 增长快,但根本问题不在碎片本身,而在写放大。
- 别迷信
OPTIMIZE TABLE:刚优化完,下一次UPDATE就又开始分裂 - 别盲目调高
innodb_fill_factor:设成 99 反而让页“刚满就裂”,尤其对二级索引无效甚至有害 - 别把
update_at或status往联合索引前缀里塞:它们更新频繁,放越前,分裂越猛
删掉没被 WHERE 或 JOIN 用到的索引最立竿见影
很多表的索引是历史遗留,实际查询根本没走。比如有个 idx_status,但所有查询都靠 user_id 过滤,status 仅用于应用层判断——这个索引纯属写入负担。
操作前先验证:
- 查
information_schema.STATISTICS确认该索引是否出现在EXPLAIN计划里 - 在备库执行
ALTER TABLE t DROP INDEX idx_status,压测对比UPDATE延迟是否下降 30%+ - 观察
Handler_read_rnd_next是否明显回落(说明随机回表减少)
删一个无用索引,往往比调参、重建更有效,且零风险。
把高频更新字段移出联合索引前缀,改用覆盖+过滤
例如原索引是 INDEX idx_user_status_created (user_id, status, created_at),而业务常查 WHERE user_id = ? AND status = ?,但 status 每分钟更新多次。这时应改为:
INDEX idx_user_created (user_id, created_at)
再配合 SELECT id, user_id, status, created_at FROM t WHERE user_id = ?,让查询走覆盖索引,应用层用 status 做二次过滤。虽然多传几个字段,但避免了每次 UPDATE status 都要分裂索引页。
- 等值查询仍能命中
user_id前缀,效率不降 -
status不再参与索引排序和定位,分裂概率大幅降低 - 若后续需按
status范围查,可单独建只读场景索引,而非强绑主业务索引
MySQL 8.0.23+ 优先用 ALTER TABLE ... REBUILD 替代 OPTIMIZE TABLE
REBUILD 是目前最可控的在线重建方式:它只重排数据页和索引页,不重算统计信息,因此不会引发执行计划突变;也不像 OPTIMIZE TABLE 那样可能因外键或全文索引退化为 COPY 模式、锁全表。
正确流程是:
- 先执行
ALTER TABLE t REBUILD - 再单独跑
ANALYZE TABLE t更新统计信息(按需,非必须立即) - 避开业务高峰,监控
Innodb_pages_written和innodb_buffer_pool_reads是否短暂冲高
注意:REBUILD 不解决根本问题,只是“轻量清场”。真正要控分裂,还得回到索引设计本身——字段要不要进索引、放在什么位置,比怎么重建更重要。











