optimize table 仅在 innodb_file_per_table=on 且 data_free 显著时才真正缩小 .ibd 文件;验证须查 information_schema.tables.data_free,而非 df -h,并确认磁盘空间充足、无长事务、主从协调。

OPTIMIZE TABLE 对 InnoDB 表只有在满足特定条件时才真正缩小 .ibd 文件;不满足就白跑,磁盘空间一丁点都不会少。
怎么判断一张表到底有没有碎片可回收
别凭感觉,直接查 information_schema.TABLES.DATA_FREE。这个值不是“空闲空间”,而是 InnoDB 预估的、因页分裂或删除未复用导致的内部碎片字节数。
- 如果
DATA_FREE接近DATA_LENGTH + INDEX_LENGTH(比如 >30%),说明碎片率高,值得干预 - 如果
DATA_FREE是 0 或几 KB,执行OPTIMIZE TABLE几乎不会让.ibd变小 - 查所有碎片 >100MB 的表:
SELECT TABLE_SCHEMA, TABLE_NAME, sys.FORMAT_BYTES(DATA_FREE) AS fragment FROM information_schema.tables WHERE DATA_FREE > 100*1024*1024 AND ENGINE='InnoDB'; - 注意:
DATA_FREE对innodb_file_per_table=OFF的共享表空间(ibdata1)恒为 0,此时该字段无效
为什么 OPTIMIZE TABLE 执行完 df -h 看不到磁盘变大
这不是命令没生效,而是你验证方式错了。Linux 的 df -h 显示的是整个文件系统维度的统计,受缓存、延迟更新、RDS 封装等干扰,根本不能用来判断单个表是否缩容。
- 真正有效的验证方式是进数据库数据目录,直接比对文件大小:
ls -lh /var/lib/mysql/db_name/tbl_name.ibd -
OPTIMIZE TABLE在innodb_file_per_table=ON下会原子替换旧.ibd文件,新文件写完后 unlink 旧文件,所以.ibd大小下降是真实发生的 - 如果
.ibd没变小,优先检查:SELECT @@innodb_file_per_table;是否为 1;SHOW CREATE TABLE tbl_name;输出里是否有ROW_FORMAT或CREATE_OPTIONS提示独立表空间 - 某些托管服务(如阿里云 RDS)的“磁盘使用率”是聚合统计,可能滞后数分钟甚至更久
替代方案:ALGORITHM=INPLACE 能不能代替 OPTIMIZE TABLE
能,但目标不同——它解决的是“逻辑紧凑”,不是“磁盘瘦身”。
-
ALTER TABLE tbl_name ENGINE=InnoDB ALGORITHM=INPLACE LOCK=NONE;会重建表页结构、清空DATA_FREE,但.ibd物理大小不变 - 优势是几乎不锁 DML(仅短时间元数据锁),适合线上大表;缺点是不释放磁盘空间
- 如果你只是想提升查询效率、减少页分裂影响,用这个;如果目标是腾出磁盘空间,必须用
OPTIMIZE TABLE或更彻底的方式(如mysqldump导出再导入) - 注意:
ALGORITHM=INPLACE要求 MySQL ≥5.6,且不能与长事务、备份进程(如mysqldump --single-transaction)并发执行
执行前必须卡死的三个检查点
线上误操作一次,就可能引发锁表、磁盘打满、主从延迟甚至中断。
- 确认磁盘剩余空间 ≥ 当前表的
DATA_LENGTH + INDEX_LENGTH(重建过程需要双倍空间) - 运行
SHOW PROCESSLIST;,状态为Waiting for table flush或存在长时间未提交事务?必须先 kill 掉,否则OPTIMIZE TABLE会无限等待 - 主库避开业务高峰;从库执行前必须先
STOP SLAVE;,否则 relay log 应用延迟可能导致主从不一致 - 额外提醒:如果表用了
TEXT/VARCHAR(2000)字段建了索引,索引体积可能远超数据本身——这种表即使缩容,收益也有限,应优先考虑删索引或改用前缀索引
最常被忽略的一点:OPTIMIZE TABLE 不是“整理碎片”的轻量操作,它是重建整张表。哪怕你只删了 1 行,只要 DATA_FREE 显著,它就得拷贝全部有效数据、重写全部索引、生成新 .ibd。代价和风险都得提前算清楚。











