表碎片率超20%且EXPLAIN显示rows远大于filtered*rows、慢查询频繁出现Using temporary/filesort或平均行大小超业务字段2倍以上时,才需执行OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB;否则可能无效甚至更慢。

表碎片率超 20% 时,OPTIMIZE TABLE 或 ALTER TABLE ENGINE=InnoDB 是最直接有效的解法,但盲目执行大概率白忙、卡住甚至让查询更慢——关键得先确认是不是真碎片在作祟。
怎么判断真是索引碎片拖慢了查询?
别只看 Data_free 高或磁盘快满了。真正要盯的是它是否实际影响了扫描效率:
-
DATA_FREE / (DATA_LENGTH + INDEX_LENGTH) > 0.2是硬门槛,低于这个值基本不用动 -
EXPLAIN中rows远大于filtered * rows(比如rows=50000但filtered=10),说明 B+ 树页内空洞多,扫描行数虚高 - 慢查询日志里反复出现
Using temporary或Using filesort,且没走覆盖索引,大概率是碎片导致临时表膨胀、排序缓存命中率下降 - 算一下平均行大小:
SELECT (data_length + index_length) / table_rows AS avg_row_size FROM information_schema.tables,如果比业务字段总和大 2 倍以上,说明页填充率极差
执行前必须检查的三件事
缺一不可,否则命令会卡住、失败或无效:
解析 MySQL 8.0 的 DDL 脚本,并将表结构导出为 Excel 格式。适用于用户需将 MySQL 的 `CREATE TABLE` 语句转换为结构化的 Excel 文件场景,导出内容包含字段名、数据类型、长度及精度等信息。支持多张表的批量处理,每张表对应 Excel 中的一个独立工作表。适用于数据库文档编写、数据字典生成及模式分析等任务。
- 磁盘剩余空间 ≥ 当前表
.ibd文件大小(重建过程先写新文件再原子替换) - 查
SELECT @@innodb_file_per_table,结果必须为1;若为0,所有表共用ibdata1,OPTIMIZE TABLE根本无法把空间还给操作系统 - 确认无长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE trx_started,有结果就别动,等它结束或KILL
用 OPTIMIZE TABLE 还是 ALTER TABLE ENGINE=InnoDB?
生产环境优先选后者,更可控:
- 语义明确,行为稳定;MySQL 5.6+ 支持显式控制锁级别:
ALTER TABLE t ENGINE=InnoDB ALGORITHM=INPLACE LOCK=NONE(MySQL 8.0+ 才真正生效) - 不会自动触发
ANALYZE TABLE,避免统计信息突变导致执行计划抖动 - 不重置
AUTO_INCREMENT值,也不影响外键或触发器定义 - 效果完全等同于
OPTIMIZE TABLE(InnoDB 下本质就是隐式ALTER TABLE ENGINE=InnoDB),但更轻量
清理完还是慢?重点看这三点
优化不是万能解药,很多“无效”其实是后续动作没跟上:
- 刚优化完首条查询必然慢——
innodb_buffer_pool_size不够大时,数据页全冷,得等缓存热起来;别拿第一条响应时间当基准 -
OPTIMIZE TABLE会重置统计信息,原本走idx_a_b的查询可能突然改走全表扫描;优化后应手动ANALYZE TABLE t更新统计,再观察执行计划 - 如果慢查询集中在某个高频更新字段(如
updated_at)上的单列索引,它本身就在持续制造碎片;考虑删掉该索引,或把它挪到复合索引后缀位(如idx_create_time_updated_at)
碎片清理只解决物理存储层的低效,它不修复错误的索引设计、不替代合理的查询写法、也不弥补过小的缓冲池配置。真正卡点往往藏在这些地方,而不是 .ibd 文件大小本身。

















