DELETE仅逻辑标记删除行,不释放磁盘空间;必须通过OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB重建表才能真正缩容.ibd文件,前提是innodb_file_per_table=ON且无长事务阻塞。

DELETE只是标记删除,InnoDB根本不会把页还给操作系统
执行 DELETE FROM t WHERE ... 后 du -sh t.ibd 几乎不变,不是你操作错了,也不是配置漏了——这是 InnoDB 的硬性设计。它只把行头的 delete_mask 位设为 1,并更新页内空闲链表,物理页仍保留在文件里,供后续 INSERT 复用。
哪怕 SELECT COUNT(*) = 0,只要没重建表,.ibd 尺寸就纹丝不动;Data_free 值反而可能变大,这不是泄漏,是引擎在说:“这些页我留着,下次写入直接用。”
为什么 OPTIMIZE TABLE 或 ALTER TABLE ENGINE=InnoDB 才能真正缩容
这两个命令本质一致:新建空表 → 逐行拷贝有效数据 → 重建索引 → 原子替换原 .ibd 文件 → 删除旧文件。旧页被丢弃,操作系统才真正回收空间。
关键点:
-
OPTIMIZE TABLE在 MySQL 5.7+ 等价于ALTER TABLE t FORCE,会加 MDL_EXCLUSIVE 锁,DML 被阻塞 - 必须确保
innodb_file_per_table = ON,否则所有表共用ibdata1,重建也白搭 - 不走 binlog(ROW 格式下),主从
.ibd大小会不一致;RDS 等托管服务可能禁用或仅在只读副本执行 - 全程需要额外磁盘空间 ≥ 当前表的
Data_length,写满直接报ERROR 1034
长事务会卡住空间回收,比删得少更致命
就算你删了 90% 的数据,只要存在未提交的长事务,InnoDB 的 purge 线程就不敢清理已删行——因为那些“旧版本”可能还在被该事务的快照引用。
结果就是:碎片越堆越多,Data_free 居高不下,OPTIMIZE TABLE 即使跑完,实际释放量也可能远低于预期。
快速排查:
- 查活跃长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 600 - 看 purge 进度:
SHOW ENGINE INNODB STATUS\G,搜Purge done for trx's n:o < XXXXXXXX - 确认碎片程度:
SHOW TABLE STATUS LIKE 't'\G,重点看Data_free是否远超Data_length的 10%
TRUNCATE 和 DROP 是唯二能立刻释放空间的操作
TRUNCATE TABLE t 会直接删除 .ibd 文件并新建空表,空间立刻返还 OS;DROP TABLE t 同理,且不可回滚。
但它们有强约束:
-
TRUNCATE不支持WHERE,不能删部分数据 -
TRUNCATE在有外键引用的子表上会报ERROR 1701 -
TRUNCATE不走 binlog row 事件,主从复制中可能跳过审计逻辑 - 如果
innodb_file_per_table = OFF,TRUNCATE也无法缩容ibdata1
真正容易被忽略的是:哪怕开了 innodb_file_per_table,如果这张表是老版本迁移过来、从未重建过,它可能依然躺在共享表空间里,.ibd 文件压根不存在——此时 OPTIMIZE TABLE 或 ALTER TABLE ENGINE=InnoDB 都不会生成新文件,更谈不上释放空间。得先强制迁出:ALTER TABLE t ENGINE=InnoDB ROW_FORMAT=DYNAMIC;。


















