optimize table 仅清理页级空洞,无法根治高频更新导致的索引碎片;查询未提速常因实际碎片率未超标,或优化未触及写放大与页分裂本质。

直接说结论:OPTIMIZE TABLE 对索引碎片“能清但不治本”,尤其在高频更新字段(如 status、updated_at)建了索引的场景下,刚优化完,下次 UPDATE 就又开始碎——它解决的是表结构层面的页空洞,不是写放大和页分裂的根源。
为什么 OPTIMIZE TABLE 后查询还是慢
常见错误现象是执行完 OPTIMIZE TABLE t,Data_free 下降了,但慢查询没改善,甚至 innodb_buffer_pool_reads 反而上升。这不是命令失效,而是你没对上症:
- 碎片率其实没超标:
Data_free / (Data_length + Index_length) - 真正瓶颈不在碎片,而在缓冲池冷启动:优化后
innodb_buffer_pool_size不够大,首条查询要全盘读,缓存还没热起来 - 执行计划突变:
OPTIMIZE TABLE会重置统计信息,原本走idx_created_at_status的查询,可能突然改走全表扫描 - 锁期间被长事务阻塞过:
SHOW PROCESSLIST里有Waiting for table metadata lock,说明优化实际没真正完成,只是卡住了
MySQL 8.0+ 更推荐用 ALTER TABLE ... REBUILD
ALTER TABLE t REBUILD 是比 OPTIMIZE TABLE 更干净的选择,尤其适合线上高频更新表:
- 只重排数据页和索引页,不重置统计信息 → 避免执行计划抖动
- 默认走
ALGORITHM=INPLACE,无需双倍磁盘空间(OPTIMIZE TABLE在 8.0+ 也默认 INPLACE,但语义模糊) - 不触发
ANALYZE TABLE自动行为,后续可按需手动ANALYZE TABLE t控制时机 - 注意:仅 MySQL 8.0.23+ 支持,低版本请用
ALTER TABLE t ENGINE=InnoDB ALGORITHM=INPLACE LOCK=NONE
删索引比优化索引更有效
频繁更新字段上的索引(比如 UPDATE t SET status = 'done' WHERE id = ?)本身是碎片加速器,不是查询加速器。先确认它是否真被用到:
- 查
performance_schema.table_io_waits_summary_by_index_usage,看COUNT_STAR是否长期为 0 - 在备库执行
ALTER TABLE t DROP INDEX idx_status,观察Handler_update和延迟是否下降 30%+ - 若该字段必须出现在复合索引中,把它挪到后缀位:把
idx_status_created_at改成idx_created_at_status,等值查询仍能走索引,但更新时页分裂概率大幅降低 - 别留“以防万一”的索引——每个二级索引都会在每次
UPDATE时同步刷脏页,代价远高于你想象
监控比定期优化更重要
碎片不是独立指标,它总是和别的信号一起出现。别设“每周一凌晨跑一遍 OPTIMIZE TABLE”这种定时任务,盯住这三个组合指标:
-
innodb_buffer_pool_read_requests / innodb_buffer_pool_reads周环比跌超 15% → 碎片已实质性抬高随机 I/O 比例 -
SELECT (Data_length + Index_length) / Table_rows FROM information_schema.TABLES远大于业务行实际字节数(比如算出来 1.8KB,但字段加起来才 150 字节)→ 页内空洞严重 - 慢查询里
rows明显大于filtered * rows_examined,且Extra常带Using temporary; Using filesort→ B+ 树逻辑结构已退化
真正容易被忽略的是:碎片问题从不单独发生。它总和写放大、缓冲池压力、执行计划漂移捆在一起。只清碎片不调索引、不控更新节奏,等于扫完地又往屋里泼水。











