DELETE后磁盘空间不释放是InnoDB正常设计行为,因仅逻辑标记删除行(设delete_mask位)、不物理回收页,.ibd文件大小不变;须通过OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB重建表才能真正释放空间。

DELETE后磁盘空间不释放,不是SQL写错了、不是配置漏了、也不是数据库坏了——这是InnoDB引擎的既定行为,它压根就不会把页还给操作系统。
DELETE只是设 delete_mask 位,不碰 .ibd 文件大小
InnoDB 的 DELETE 不是物理擦除,而是把行头的 delete_mask 位设为 1,并更新页内空闲链表。整页仍保留在 .ibd 文件中,供后续 INSERT 复用。
-
du -sh table.ibd输出完全不变 -
SHOW TABLE STATUS LIKE 't'\G中的Data_length几乎不降,哪怕SELECT COUNT(*) = 0 -
Data_free值反而可能变大——它代表“内部可复用但未归还 OS”的页数,不是已释放空间 - 这和 MVCC 强相关:未提交的长事务会让 purge 线程不敢清理旧版本,导致已删数据滞留更久
真正释放空间必须重建表,OPTIMIZE TABLE 和 ALTER TABLE ENGINE=InnoDB 效果一致
两者在 InnoDB 表上都走同一路径:新建空表 → 拷贝所有未被标记删除的有效行 → 原子替换 .ibd 文件 → OS 回收旧文件。
- 命令等价:
OPTIMIZE TABLE t在 MySQL 5.7+ 就是ALTER TABLE t FORCE,而ALTER TABLE t ENGINE=InnoDB语义更直白 - 它们都不走 binlog(ROW 格式下),主从
.ibd大小会不一致 - 全程加 MDL 锁两次(开始前 + 替换后),期间若遇慢查询或大事务,容易卡在 “Waiting for meta data lock”
- 对刚建完就删光的小表,
.ibd可能也不变小——新页本身就很紧凑,没多少空洞可填
执行前不检查这三件事,大概率失败甚至阻塞全库
盲目运行重建命令,轻则锁表数小时,重则写满磁盘、残留临时表、拖垮主从同步。
- 确认
innodb_file_per_table = ON:否则重建无效,空间仍卡在ibdata1里 - 查长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 600,有结果必须先KILL - 磁盘剩余空间 ≥ 当前表
Data_length的 1.5 倍:临时表 + redo/undo 日志都要落盘,否则报ERROR 1034
RDS/PolarDB 等托管服务常禁用或改写 OPTIMIZE TABLE
很多云厂商默认禁用该命令,或自动转到只读副本执行,结果是你在主库跑完,监控却看不到空间下降。
- 必须提前查控制台文档或执行
SHOW VARIABLES LIKE 'have_op%'验证支持状态 - 某些 RDS 版本会把
OPTIMIZE TABLE自动转成只读副本上的后台任务,主库 DML 不阻塞,但空间回收延迟不可控 - 如果表带外键依赖或使用分区表,
ALTER TABLE t ENGINE=InnoDB可能触发隐式锁升级,比OPTIMIZE TABLE更激进
最易被忽略的是:Data_free 高 ≠ 一定要立刻重建;碎片是否影响性能,得看实际 INSERT 延迟和页分裂率,而不是盯着 df -h 看数字。


















