innodb 的 delete 不释放磁盘空间是正常行为,仅标记删除而不物理清除;真正收缩需 optimize table、drop/import 或 pt-online-schema-change。

删掉一半数据,.ibd 文件大小纹丝不动,不是 MySQL 出 bug 了,是 InnoDB 的正常行为——它根本没“物理删除”,只是打了个删除标记,等着复用。
为什么 DELETE 不释放磁盘空间
InnoDB 的数据页(page)是固定大小(默认 16KB),所有增删改都在页内做标记或移动。执行 DELETE FROM t WHERE id > 1000 时:
- 每条匹配记录被标记为“已删除”(record header 中的
deleted_flag置 1),但仍在原页中占位 - 该数据页本身不会被立即回收,除非整页所有记录都被标记删除,且后续有 purge 线程真正清理(仍不保证释放磁盘)
- 即使你删光全表,
.ibd文件也不会缩,因为文件系统层面的空间未归还给操作系统 - 唯一能触发文件收缩的操作是
DROP TABLE(前提是innodb_file_per_table = ON)
如何判断表是否存在严重空洞
别猜,用 information_schema 查真实碎片率:
SELECT table_name, round((data_length + index_length) / 1024 / 1024, 2) AS size_mb, round(data_free / 1024 / 1024, 2) AS free_mb, round(100 * data_free / (data_length + index_length + data_free), 2) AS frag_pct FROM information_schema.tables WHERE table_schema = 'your_db' AND table_name = 'your_table';
重点看 data_free 和 frag_pct:
-
data_free > 0说明存在可复用但未归还的空间 -
frag_pct > 25%通常意味着空洞较严重,值得优化 - 注意:刚执行完大量
DELETE后,data_free可能还没立刻更新,等几秒或执行SHOW TABLE STATUS刷新
OPTIMIZE TABLE 能不能直接用
能,但要清楚它在做什么、代价是什么:
- 对 InnoDB 表,
OPTIMIZE TABLE t实际等价于ALTER TABLE t ENGINE=InnoDB(重建表) - 它会创建新表、逐行拷贝数据、重建索引,最后原子替换,因此能彻底消除空洞、压缩页、提升查询效率
- 但过程中会加
SNAPSHOT级锁(MySQL 5.6+ 支持并发 DML,但仍有性能抖动;5.7+ 支持ALGORITHM=INPLACE的部分场景) - 大表执行时间长,且临时需要 2 倍磁盘空间(新旧表并存阶段)
- 不建议在业务高峰跑;如果表超 10GB,优先考虑
pt-online-schema-change或分批导出导入
更稳妥的收缩方案:mysqldump + DROP + IMPORT
当 OPTIMIZE TABLE 不可控(比如主从延迟敏感、磁盘紧张),手动重建更透明:
- 导出结构和数据:
mysqldump -u root -p --no-create-info your_db your_table > t_data.sql - 导出建表语句(含索引):
mysqldump -u root -p --no-data your_db your_table > t_schema.sql - 重命名原表:
RENAME TABLE your_table TO your_table_bak; - 执行
t_schema.sql创建空表,再执行t_data.sql导入 - 验证无误后
DROP TABLE your_table_bak;
这个流程完全绕过 InnoDB 内部标记机制,生成的是紧凑的全新 .ibd 文件。缺点是停写窗口稍长,且需确保导入期间无写入发生。
空洞不是错误,是 InnoDB 为高并发写的妥协;真正要警惕的,是长期不维护导致的索引深度增加、缓冲池命中率下降、备份体积膨胀——这些比文件多占几个 GB 更伤业务。











