delete不释放磁盘空间是innodb正常行为,因仅逻辑删除、标记行并更新空闲链表,.ibd文件大小不变;optimize table可重建表释放空间,但需满足innodb_file_per_table=on、无长事务阻塞purge、足够磁盘冗余等前提。

DELETE 后磁盘空间没释放,不是 bug,也不是配置错,是 InnoDB 引擎的正常行为——它根本就不会把数据从 .ibd 文件里物理擦掉。
为什么 DELETE 不会缩 .ibd 文件大小
InnoDB 的 DELETE 是逻辑删除:只把行头的 delete_mask 位设为 1,并更新页内的空闲链表,整页仍保留在文件中,供后续 INSERT 复用。
这意味着:
-
du -sh table.ibd输出完全不变 -
SHOW TABLE STATUS LIKE 't'\G中的Data_length几乎不降 - 即使
SELECT COUNT(*) = 0,文件尺寸也纹丝不动 -
Data_free值反而可能变大(说明“可复用但未归还 OS”的页变多了)
这不是空间泄漏,而是为 MVCC、并发写入和写放大控制做的权衡。
OPTIMIZE TABLE 能释放空间,但不是“一键清理”
OPTIMIZE TABLE t 在 InnoDB 下等价于 ALTER TABLE t ENGINE=InnoDB,本质是重建表:
新建空表 → 拷贝所有未被标记删除的有效行 → 替换原 .ibd 文件 → 旧文件由 OS 回收。
但它有三个硬性前提,缺一不可:
-
innodb_file_per_table = ON(否则删再多也缩不了ibdata1) - 当前无长事务阻塞 purge(查
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 600) - 磁盘剩余空间 ≥ 当前表的
Data_length(建议留 1.5 倍冗余,否则卡在 ERROR 1034)
另外要注意:
- 它全程加 MDL 锁,高峰期容易卡住(尤其遇到
SELECT ... FOR UPDATE或慢查询) - 不走 binlog(ROW 格式下),主从
.ibd大小会不一致 - 对刚建完就删光的小表,
OPTIMIZE后.ibd可能也不变小(新页本身就很紧凑)
什么情况下不该直接跑 OPTIMIZE TABLE
盲目重建大表,风险远高于收益:
- 表 >50GB 且磁盘使用率 >85%:临时文件极易写满,操作中断后残留临时表,更难清理
- 业务不能停写:锁表期间 DML 会被阻塞,超时或连接池耗尽很常见
- 数据只是阶段性归档(比如只保留最近 6 个月):更适合导出再重建
可选替代方案:
- 全表清空且无外键依赖:
TRUNCATE TABLE t(立刻释放空间,但不支持WHERE) - 按条件归档:
mysqldump --where="create_time > '2025-01-01'" db t > t_recent.sql,再DROP+CREATE+source - 线上大表无法停机:
pt-online-schema-change --alter="ENGINE=InnoDB" --execute(仍需双倍磁盘空间,且要确认ALGORITHM=INPLACE是否生效)
真正容易被忽略的点
Data_free 高 ≠ 必须优化。它只是当前“可复用页数”的统计值,只要业务写入稳定、没有慢查询卡住 purge、也没有长时间未提交事务,这些空间本就是为性能预留的缓冲区。
重建表不是运维 checklist 上的固定动作,关键在于判断:
- 这块空间是否真的影响了磁盘水位告警?
- 重建带来的 I/O 压力和锁开销,业务能否承受?
- 有没有更轻量、更可控的方式达成目标?
别把“看起来有碎片”当成必须动手的理由。











