DELETE不释放磁盘空间是InnoDB正常机制:仅逻辑标记删除、不物理擦除页,Purge线程异步清理行但不归还页给OS;必须通过OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB重建表才能真正释放空间。

DELETE 不释放磁盘空间是 InnoDB 的正常行为,不是 bug,也不是配置错误。它只做逻辑标记,不删物理页,更不缩 .ibd 文件。
为什么 DELETE 后 data_length 和 du -sh table.ibd 都不变
InnoDB 的 DELETE 实际只是把行头的 delete_mask 位设为 1,并更新页内的空闲链表,整块数据页仍保留在文件里等待复用。这意味着:
-
Data_length是有效数据页的累计大小,已删行不参与统计,但页本身没被丢弃,所以值不变 -
Data_free通常会变大——它代表“当前可复用但未归还 OS”的空页数,不是已释放空间 -
du -sh /var/lib/mysql/db/t.ibd看的是文件系统级大小,InnoDB 不主动截断文件 - 即使
SELECT COUNT(*) = 0,只要没重建表,.ibd就纹丝不动
OPTIMIZE TABLE 和 ALTER TABLE t ENGINE=InnoDB 到底做了什么
两者在 InnoDB 表上效果一致:都是重建表。本质是执行以下原子操作:
- 新建一个空的
.ibd文件,按当前索引结构重排聚簇索引 - 逐行扫描原表,只拷贝未被标记删除的有效行
- 拷贝完成后,原子性地
DROP原表、RENAME新表 - OS 层面真正回收旧文件占用的空间
注意:OPTIMIZE TABLE t 在 MySQL 5.7+ 等价于 ALTER TABLE t FORCE,默认加 S 锁(允许读写但性能抖动明显);而 ALTER TABLE t ENGINE=InnoDB 更适合脚本批量调用,语义也更直白。
执行前必须验证的三件事,缺一不可
跳过任意一项,都可能写满磁盘、锁死业务或中途失败:
- 查磁盘余量:
df -h /var/lib/mysql,确保剩余空间 ≥SHOW TABLE STATUS LIKE 't'\G中的Data_length(建议留 1.5 倍冗余) - 查长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 600,有结果就得先处理掉——否则purge线程卡住,重建也无法清理干净 - 确认引擎:
SHOW CREATE TABLE t\G输出中必须含ENGINE=InnoDB;MyISAM 表虽支持OPTIMIZE,但机制完全不同,此处不适用
什么情况下不该直接跑 OPTIMIZE TABLE
对活跃大表盲目重建,代价可能远超收益:
- 表体积 >100GB 或磁盘使用率 >85% 时,临时表极易写满磁盘,触发
ERROR 1034 (HY000): Incorrect key file for table - 外键子表上不能直接
TRUNCATE TABLE(报错ERROR 1701),但TRUNCATE是唯一能立刻释放空间且不依赖磁盘余量的操作 -
Data_free高 ≠ 必须优化——它本就是为 INSERT 复用预留的缓冲,只要业务写入稳定、无慢查询,这些空间就是健康的 - RDS/PolarDB 等托管服务中,
OPTIMIZE TABLE可能被禁用或转到只读副本执行,需查控制台文档确认是否支持
真正容易被忽略的是:重建表不是“删完就跑”的操作,关键在判断是否真需要释放空间、以及能否承受重建过程中的 I/O 和锁开销。


















