频繁更新字段上的索引不是碎片主因而是写放大和页分裂加速器;OPTIMIZE TABLE治标不治本,删无用索引、调整复合索引顺序、用REBUILD替代是更优解。

频繁更新字段上的索引本身不是“碎片来源”的主因,而是写放大和页分裂的加速器;OPTIMIZE TABLE 能清碎片,但对这类表往往治标不治本,甚至可能让下次更新更快变慢。
为什么 status/update_at 这类字段建索引后碎片增长特别快
每次 UPDATE 修改这些字段,InnoDB 不仅要改聚簇索引(主键页),还要同步更新所有含该字段的二级索引页——哪怕查询根本不用它。B+ 树页内空闲空间(gap)快速积累,页间物理顺序也因频繁分裂而打散。
- 典型表现:
SHOW TABLE STATUS中Data_free每天涨几十 MB,innodb_buffer_pool_reads持续上升 - 不是“磁盘碎片”,是 B+ 树逻辑结构退化:页利用率常低于 50%,随机 I/O 比例翻倍
- 碎片率计算应基于
Data_free / (Data_length + Index_length),>20% 才算严重;单看Data_free > 0会误判
OPTIMIZE TABLE 对高频更新字段索引的实际效果有限
它确实会重建所有索引页、释放空闲空间、重排物理顺序,但问题在于:刚优化完,下一次 UPDATE 就又开始制造碎片。尤其当索引字段本身更新极频繁时,优化收益窗口极短。
-
OPTIMIZE TABLE t在 MySQL 8.0+ 默认走ALGORITHM=INPLACE,但仍需持有 S 锁,阻塞INSERT/UPDATE/DELETE - 若表含全文索引或外键,自动降级为
COPY模式,触发 X 锁,连SELECT都被卡住 - 统计信息重置后,可能让原本走索引的查询突然改用全表扫描——执行计划突变更危险
比 OPTIMIZE TABLE 更有效的三类操作
与其反复清理,不如从源头控制碎片生成速度。以下方案按实施成本由低到高排列:
- 删掉真正没被
WHERE或JOIN用到的索引:ALTER TABLE t DROP INDEX idx_status;先在备库验证删除后UPDATE延迟是否下降 30%+ - 把高频更新字段移出复合索引前缀:
idx_created_at_status改为idx_status_created_at,让等值查询仍能走索引,但降低更新时页分裂概率 - MySQL 8.0.23+ 可用
ALTER TABLE t REBUILD替代OPTIMIZE TABLE:只重排数据页和索引页,不更新统计信息,避免执行计划抖动;后续再单独ANALYZE TABLE
真正容易被忽略的点
碎片不是独立存在的性能问题,它总是和写放大、缓冲池压力、执行计划稳定性捆绑出现。监控时别只盯 Data_free,更要对比 innodb_buffer_pool_read_requests / innodb_buffer_pool_reads 的周环比——如果这个比值连续三天跌超 15%,说明碎片已开始实质性影响缓存效率,此时才值得动 REBUILD 或调整索引结构。


















