optimize table 对 innodb 无效常因未启用 innodb_file_per_table、无真实碎片或存在长事务;data_free 不降不等于失败,需结合 avg_row_size、delete 比例及执行计划判断碎片;优先选用可控重建方案。

OPTIMIZE TABLE 为什么有时没效果
MySQL 的 OPTIMIZE TABLE 并不是万能的“一键瘦身”命令,它只对 MyISAM 和 InnoDB 表生效,且行为差异很大。对 InnoDB 来说,它本质是重建表(ALTER TABLE ... FORCE),需要足够磁盘空间、会锁表(8.0+ 支持在线 DDL 但仍有条件限制),并且——如果表本身没有明显碎片(比如刚做过大量 DELETE 且未做 innodb_file_per_table=ON 下的收缩),执行后 Data_free 值可能几乎不变。
常见错误现象:SHOW TABLE STATUS 显示 Data_free 仍高达几 GB,但 OPTIMIZE TABLE 执行成功、时间很短、磁盘占用没降。
- 确认是否启用了
innodb_file_per_table=ON(必须开启才能真正释放空间回操作系统) - 检查表是否被其他事务长时间持有(导致无法重建)
- 注意
OPTIMIZE TABLE对InnoDB实际调用的是ALTER TABLE ... ENGINE=InnoDB,若表有全文索引或虚拟列,可能触发隐式复制而非就地重建
如何判断碎片是否真该处理
别只看 Data_free。这个值在 InnoDB 中反映的是当前表空间内“未使用的页”,但它不等于可回收空间——尤其当共享表空间(ibdata1)存在时,Data_free 再大也无法释放给系统。
真正该清理的信号是:
-
SELECT (data_length + index_length) / table_rows AS avg_row_size FROM information_schema.tables结果远大于实际业务行平均大小(比如字段总和才 200 字节,却算出 2KB) - 历史
DELETE比例高(>30%),且之后几乎没有INSERT或UPDATE回填 - 慢查询日志中频繁出现
Using temporary; Using filesort,且执行计划显示扫描行数远超结果行数(说明 B+ 树深度/空洞影响了范围扫描效率)
替代方案:比 OPTIMIZE TABLE 更可控的操作
直接跑 OPTIMIZE TABLE 在生产环境风险高。更稳妥的做法是按需选择重建方式:
- 小表(ALTER TABLE t ENGINE=InnoDB(显式指定引擎,语义清晰,兼容性好)
- 大表且要求低影响:用
pt-online-schema-change --alter "ENGINE=InnoDB" D=db,t=t(Percona Toolkit,支持无锁重建,但依赖触发器和额外磁盘) - 只想释放空间不改结构:确认
innodb_file_per_table=ON后,先ALTER TABLE t ROW_FORMAT=COMPACT(或DYNAMIC),再OPTIMIZE TABLE,能提升页利用率
注意:OPTIMIZE TABLE 会重置 AUTO_INCREMENT 计数器(仅 MyISAM),InnoDB 不会;但重建过程会让二级索引重新排序,对后续范围查询有真实收益。
执行后空间没释放?检查这几个地方
即使 OPTIMIZE TABLE 成功,磁盘空间没变少,大概率卡在这几个环节:
-
innodb_file_per_table是OFF:所有表数据写进ibdata1,优化只能整理内部页,无法 shrink 文件 - 操作系统层面文件没被回收:Linux 下删除大文件后空间要等进程关闭句柄,检查
lsof | grep ibdata1是否还有 mysqld 持有旧句柄 - binlog 或 relay log 占用大量空间,误以为是表碎片(用
du -sh /var/lib/mysql/mysql-bin.*验证) - 执行完没刷新
information_schema缓存:运行FLUSH TABLES再查SHOW TABLE STATUS
最常被忽略的是:OPTIMIZE TABLE 只作用于单表,而真正的空间压力往往来自 ibdata1 或 ib_logfile* ——这些没法靠它解决。











