innodb的delete仅逻辑标记删除,不归还页给操作系统;必须通过optimize table或alter table engine=innodb重建表才能真正释放.ibd文件空间。

DELETE 不释放磁盘空间,是 InnoDB 的设计行为,不是 bug,也不是配置错误。
DELETE 只是逻辑删除,不归还页给操作系统
InnoDB 以 16KB 的页(Page)为单位管理数据。执行 DELETE 时,引擎只是把对应记录标记为“已删除”,并将其加入内部的空闲链表(free list),供后续 INSERT 复用。这些页仍属于该表的 .ibd 文件,文件大小不会变小。
常见错误现象:
-
SELECT COUNT(*)返回 0,但du -sh table.ibd和删前几乎一样 -
SHOW TABLE STATUS中Data_length不降,Data_free可能反而增大(碎片加剧) - 磁盘使用率持续高位,
df -h看不到任何变化
根本原因:InnoDB 不会主动把“可复用页”交还给文件系统。它只在重建表时,才真正丢弃旧页、写入新页,从而触发文件截断。
OPTIMIZE TABLE 为什么有时也不缩容?
OPTIMIZE TABLE 实际上是 ALTER TABLE ... ENGINE=InnoDB 的语法糖,它会重建整张表——创建新 .ibd,逐行拷贝有效数据,再删掉旧文件。但能否缩小体积,取决于当前有多少真实空洞可被合并。
关键判断条件:
- 必须满足
innodb_file_per_table = ON(默认开启,但老实例可能关着) -
SHOW TABLE STATUS中Data_free值显著(比如 >512MB),说明有大量未复用页 - 表经历过大量随机
DELETE/UPDATE,而非刚插入就清空(后者本身就很紧凑) - MySQL 5.7+ 支持
ALGORITHM=INPLACE,但大表仍耗 I/O;8.0 默认用 inplace,但仍有短暂排他锁
注意:OPTIMIZE TABLE 不走 binlog row event(除非显式开启 log_bin),从库不会同步该操作,主从 .ibd 大小可能不一致。
TRUNCATE TABLE 能立刻缩容,但限制极多
TRUNCATE TABLE 是删文件 + 新建空表,所以 .ibd 瞬间变小。但它不是通用解法:
- 不能带
WHERE条件,只能整表清空 - 不支持事务回滚(DDL 操作)
- 若该表被其他表外键引用,会直接报错
ERROR 1701 (HY000) - 在
innodb_file_per_table = OFF下,TRUNCATE对ibdata1无效 - 跳过触发器、不记录完整 binlog,审计或复制链路可能出问题
适用场景:确认要清空整表、无外键依赖、无活跃触发器、且能接受短暂停写。
真正释放空间必须重建表,但线上不能硬来
InnoDB 没有“在线收缩 .ibd”的能力。所有释放磁盘空间的操作本质都是重建——只是方式不同:
-
OPTIMIZE TABLE t:最常用,但 5.7 及以前全表锁;8.0 改进但仍需短暂 metadata lock -
ALTER TABLE t ENGINE=InnoDB:语义更清晰,适合脚本化,效果等同OPTIMIZE -
mysqldump + DROP + CREATE + INSERT:可控性强,但停机时间长,binlog 同步易断 -
pt-online-schema-change:唯一接近“在线”的方案,通过影子表 + 触发器同步变更,但需额外资源和权限
容易被忽略的关键点:
- 别在磁盘剩余
- 别在从库上直接跑
OPTIMIZE或ALTER,主从空间状态会脱节 - 长期高频删改的业务,必须配合
innodb_purge_threads调优,否则 purge 线程跟不上,空洞永远卡住











