delete后磁盘空间不释放是innodb正常设计行为,因仅标记删除而不物理回收;optimize table可收缩.ibd文件,但须满足innodb_file_per_table=on、无长事务、磁盘空间充足三个前提。

DELETE 后磁盘空间不释放,是 InnoDB 的设计行为,不是故障;OPTIMIZE TABLE 能收缩 .ibd 文件,但必须满足三个硬性前提,否则会卡住、锁死或写满磁盘。
为什么 DELETE 不释放空间
InnoDB 的 DELETE 只是把行标记为“可复用”,并不归还给操作系统。数据文件(如 test.ibd)大小完全不变,SHOW TABLE STATUS 中的 Data_length 和 du -sh *.ibd 都不会下降。这不是配置错,也不是 bug——它是 MVCC 和 purge 机制共同决定的:未提交的长事务会阻塞 purge 线程,已删记录就一直占着页。
常见错误现象包括:SELECT COUNT(*) 返回 0,但 Data_free 反而变大(碎片加剧);df -h 看不到磁盘释放;TRUNCATE TABLE 能立刻缩容,但 DELETE FROM t WHERE ... 完全无效。
OPTIMIZE TABLE 在 MySQL 5.7 上的实际行为
MySQL 5.7 的 OPTIMIZE TABLE 本质是 ALTER TABLE t ENGINE=InnoDB,它重建整张表:读出所有有效行 → 写入新页 → 替换原 .ibd 文件。因此它能真正释放空间,但代价明确:
- 全程加
MDL_EXCLUSIVE锁,所有 DML 和大部分 SELECT 都被阻塞 - 需要额外磁盘空间 ≥ 当前表的
Data_length(临时表阶段写满就报ERROR 1034) - 不走 binlog(ROW 格式下),从库不会同步该操作,主从
.ibd大小会不一致 - 即使
innodb_file_per_table=ON,若表本身已很紧凑(比如刚 INSERT 就 DELETE),OPTIMIZE后.ibd大小也可能不变
执行前必须检查的三件事
跳过任意一项,OPTIMIZE TABLE 就可能卡死或引发事故:
- 查磁盘余量:
df -h /var/lib/mysql,确认剩余空间 >SHOW TABLE STATUS LIKE 't'\G中的Data_length - 查长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 60;,有结果就得先 kill - 确认引擎和独立表空间:
SHOW CREATE TABLE t\G必须含ENGINE=InnoDB且实例已启用innodb_file_per_table=ON
替代方案:什么情况下不该用 OPTIMIZE TABLE
当表超过 50GB 或磁盘使用率 >85% 时,OPTIMIZE TABLE 风险极高。更稳妥的做法是:
- 整表清空且允许停机:直接
TRUNCATE TABLE t(注意外键约束会报ERROR 1701) - 只保留近期数据:用
mysqldump -u root -p --where="create_time > '2025-01-01'" db t > t_recent.sql导出,再DROP+CREATE+source - 线上大表无法停机:改用
pt-online-schema-change --alter="ENGINE=InnoDB" --execute,但它仍需双倍磁盘空间,且不能在从库上运行
最易被忽略的一点:OPTIMIZE TABLE 不是“越勤快越好”。频繁执行反而会因临时表开销增大 Data_free,让碎片问题更隐蔽。重点应放在预防——比如避免超长事务、按月分区归档、监控 Data_free / Data_length 比值是否持续 > 25%。











