MySQL大量DELETE后磁盘空间不释放是InnoDB正常设计,因仅逻辑标记行可复用而不物理回收页;必须通过OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB重建表才能真正缩容.ibd文件。

MySQL在大量DELETE后不自动回收磁盘空间,这是InnoDB的正常行为,不是bug;OPTIMIZE TABLE本质是重建表,才能真正释放.ibd文件占用的空间。
为什么DELETE不释放磁盘空间
InnoDB的DELETE只是把行标记为“可复用”,不会物理删除页或归还空间给操作系统。即使SELECT COUNT(*)返回0,du -sh table.ibd和SHOW TABLE STATUS里的Data_length也完全不变。
- 碎片来自页内空闲空间未合并,B+树节点分裂后残留空隙
- 长事务会阻塞purge线程,已删记录一直卡在undo日志和页中
-
innodb_file_per_table=OFF时,所有表共享ibdata1,OPTIMIZE TABLE根本无效
OPTIMIZE TABLE到底做了什么
它不是“整理碎片”的轻量操作,而是完整重建:创建新.ibd → 逐行拷贝有效数据 → 重写聚簇索引和二级索引 → 原子替换文件 → 删除旧.ibd。
- 对InnoDB来说,
OPTIMIZE TABLE t等价于ALTER TABLE t ENGINE=InnoDB(MySQL 5.6+) - 过程全程加
MDL_EXCLUSIVE锁,DML和大部分SELECT都会被阻塞 - 不写binlog(ROW格式下),从库不会同步该操作,主从
.ibd大小可能不一致
执行前必须检查的三件事
跳过任意一项,操作大概率失败或引发事故:
- 查磁盘余量:
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更稳妥的替代方案
真正治本不在“删完怎么收尾”,而在“怎么删”:
- 分批删:
DELETE FROM t WHERE id BETWEEN 10000 AND 20000,每次万级,配合SLEEP(0.1) - 分区表直接
DROP PARTITION,零碎片、秒级、不锁全表 - 清空用
TRUNCATE TABLE(注意不可回滚、重置自增、需DROP权限) - 真要重建表,优先用
ALTER TABLE t ENGINE=InnoDB ALGORITHM=COPY LOCK=SHARED,语义更明确
容易被忽略的是:DATA_FREE值没到表实际数据量的30%以上,优化几乎白费——先查SHOW TABLE STATUS LIKE 't'\G里的Data_free再决定动不动手。


















