OPTIMIZE TABLE仅在innodb_file_per_table=ON时能真正回收磁盘空间;它本质是ALTER TABLE ... ENGINE=InnoDB,重建表以缩小.ibd文件,否则仅清空Data_free而不释放磁盘。

OPTIMIZE TABLE 能回收磁盘空间,但对 InnoDB 表不总是有效——它本质是 ALTER TABLE ... ENGINE=InnoDB 的封装,只在 innodb_file_per_table=ON 且表有独立 .ibd 文件时,才能真正缩小文件体积;否则只会清空 DATA_FREE 值,不释放磁盘。
怎么判断碎片是否真占了磁盘
别只看 SHOW TABLE STATUS LIKE 't' 里的 Data_free 字段。这个值反映的是当前表空间内未被使用的页空间(字节),但它不等于磁盘上多占的空间:
- 如果
innodb_file_per_table=OFF,所有表共用ibdata1,Data_free再大也无法释放磁盘,OPTIMIZE TABLE对它完全无效 - 如果
innodb_file_per_table=ON(默认),每个表有独立.ibd文件,这时Data_free高才代表该文件内部存在可回收空间 - 更可靠的验证方式是对比
ls -lh /var/lib/mysql/db_name/t.ibd和SELECT sys.FORMAT_BYTES(data_length + index_length) FROM information_schema.tables,差值明显偏大就说明文件膨胀了
OPTIMIZE TABLE 执行时会发生什么
对 InnoDB 表执行 OPTIMIZE TABLE t,MySQL 实际做的是:
- 创建新临时表,按主键顺序重新写入全部行数据(含索引)
- 重建所有二级索引,消除叶子节点物理顺序混乱
- 丢弃旧表,重命名新表为原名
- 释放旧
.ibd文件占用的磁盘空间(仅限独立表空间)
这个过程会持有一个 S(共享)锁,允许读,阻塞写;时间取决于数据量和 I/O 性能,100 万行可能耗时数秒到几分钟。
为什么有时 OPTIMIZE TABLE 没效果
常见失效场景包括:
-
innodb_file_per_table关闭:查SELECT @@innodb_file_per_table,返回 0 就是关的,此时必须先导出数据、重装 MySQL 并开启该参数再导入 - 表里有长事务或未提交更新:
OPTIMIZE会等这些事务结束,若卡住,SHOW PROCESSLIST可看到状态为Waiting for table flush - 磁盘空间不足:重建过程需要额外空间存新表,临时空间至少要 >= 当前
.ibd大小 - 使用了压缩表(
ROW_FORMAT=COMPRESSED):某些版本下OPTIMIZE不支持压缩表在线优化,会静默失败
比 OPTIMIZE 更稳妥的替代方案
生产环境建议优先考虑 ALTER TABLE t ENGINE=InnoDB,它和 OPTIMIZE 效果一致,但语义更明确,也更容易加条件控制:
- 加
ALGORITHM=INPLACE(5.6+)可避免全拷贝,但仅适用于部分 DDL 场景,对碎片回收无效 - 加
LOCK=NONE在支持的版本中可降低写阻塞,但同样不适用于ENGINE=InnoDB重建 - 真正想无停机操作?只能靠
pt-online-schema-change工具分批迁移,不过它不减少.ibd大小,只是避免锁表
碎片回收这件事,本质上是一次“可控的破坏性重建”,没有零成本方案。最容易被忽略的一点是:别在业务高峰期跑,也别把它当成日常巡检项——碎片率低于 10% 或 DATA_FREE 时,收益远小于风险。


















