InnoDB的DELETE仅逻辑标记删除,不释放物理空间;必须通过OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB重建表才能真正释放.ibd文件空间。

DELETE只是标记删除,不是物理擦除
InnoDB 的 DELETE 操作本质是“软删除”:它不立即释放数据页空间,而是把对应记录的 deleted_flag 置为 1,并加入 purge 链表,等待后台 Purge 线程异步清理。这意味着:
- 原数据页仍保留在磁盘上,只是行被标记为“可复用”
- 如果后续插入的新记录大小 > 当前空洞,该空洞就永远无法被填充,变成永久性碎片
- 索引页(尤其是二级索引)中对应条目也被标记删除,但页本身不会自动合并或收缩
碎片直接来自页内填充率下降和页分裂残留
大量 DELETE 后,单个数据页的利用率可能从 90%+ 掉到 30% 甚至更低。而 InnoDB 不会主动把多个稀疏页合并成一个紧凑页——它只在插入时尝试复用空洞。更麻烦的是:
- 如果删除后紧接着有
INSERT(尤其是非顺序主键),InnoDB 可能触发页分裂:旧页保留部分数据,新页分配空间,但两个页都处于低填充状态 - UPDATE 变长字段(如
TEXT、VARCHAR扩容)也可能导致行迁移,原位置留下无法复用的空洞 - 二级索引更新不与聚簇索引同步,容易形成索引页内部碎片 + 跨页碎片双重叠加
data_free 值飙升但磁盘文件不缩容
执行 SELECT data_free FROM information_schema.tables 查到的值,就是当前表中所有未被复用的空洞总和。这个值可能高达几百 MB 甚至几 GB,但它完全不反映 .ibd 文件实际大小:
-
data_free是逻辑空洞统计,而.ibd文件一旦增长,就不会自动收缩(除非显式重建) - 即使
data_free = 2GB,ls -lh table_name.ibd显示的仍是原始最大尺寸 - 碎片严重时,
SELECT * FROM table_name全表扫描需读取更多页(含大量空洞页),I/O 放大明显
OPTIMIZE TABLE 或 ALTER TABLE ENGINE=InnoDB 才真正回收空间
只有重建表结构的操作,才会强制将有效数据重新写入连续页,丢弃所有空洞并重排索引:
-
OPTIMIZE TABLE table_name对 InnoDB 实际等价于ALTER TABLE table_name ENGINE=InnoDB - 该操作会加
SX锁,阻塞写入;大表执行期间可能持续数小时,必须安排在低峰期 - 要求
innodb_file_per_table = ON(默认开启),否则空间无法返还给操作系统 - 执行后
data_free归零,.ibd文件大小显著下降,B+ 树层级也常随之压缩
真正容易被忽略的是:碎片不是“删完才出现”的,而是从第一行 DELETE 开始就在积累;而等到 data_free 超过 100MB 时,往往说明空洞已广泛分布于多数数据页,此时仅靠分批 DELETE 或调整事务大小已无法逆转碎片趋势。


















