DELETE后空间不释放是InnoDB和SQL Server的正常设计,因仅逻辑标记删除行而不归还物理页;真正释放需重建表(OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB)或TRUNCATE。

DELETE 后表空间没释放,不是 SQL 写错了,也不是数据库坏了——这是 InnoDB 和 SQL Server 的默认行为,删的是“可见性”,不是物理块。
为什么 DELETE 不缩 .ibd 或 .mdf 文件
InnoDB 的 DELETE 只是把行头的 delete_mask 位设为 1,并更新页内空闲链表;SQL Server 堆表则在页里打“已删除”标记。两者都不移动数据、不归还页给操作系统,.ibd 或 .mdf 文件尺寸完全不变。
-
du -sh table.ibd、SHOW TABLE STATUS中的Data_length都不会下降 - 哪怕
SELECT COUNT(*) = 0,文件大小也纹丝不动 -
Data_free反而可能升高——它表示“内部可复用但尚未归还 OS”的页数,不是泄漏,是预留缓冲 - 启用
READ_COMMITTED_SNAPSHOT(SQL Server)或存在长事务(MySQL)时,purge 线程卡住,已删数据滞留更久
MySQL 怎么真正释放 .ibd 空间
必须重建表,让 InnoDB 把有效数据重写进新页,旧页才被丢弃,OS 才能回收。
-
OPTIMIZE TABLE t和ALTER TABLE t ENGINE=InnoDB效果一致:新建空表 → 拷贝有效行 → 替换.ibd→ 删除旧文件 - MySQL 5.7+ 中
OPTIMIZE TABLE等价于ALTER TABLE t FORCE,仍需抢两次 MDL 锁(开始前 + 替换后) - RDS/PolarDB 等托管服务常禁用
OPTIMIZE TABLE,或自动转到只读副本执行,得先查控制台文档 - 不走 binlog(ROW 格式下),主从
.ibd大小会不一致
SQL Server 堆表删完还占空间怎么办
堆表(无聚集索引)最难释放页,尤其启用了行版本控制时,TABLOCK 都可能失效。
- 全表清空 → 优先用
TRUNCATE TABLE t(快、释放页、不走日志,但重置AUTO_INCREMENT、需DROP权限) - 条件删除后想释放 → 加
TABLOCK提示:DELETE FROM t WITH (TABLOCK) WHERE ...(仅对未启用行版本控制的库有效) - 长期反复删 → 直接建聚集索引:
CREATE CLUSTERED INDEX ix_id ON t(id),后续DELETE自动触发页合并 - 慎用
DBCC SHRINKFILE:它移动页、制造碎片、可能阻塞快照读,且只对目标文件生效——如果大表不在PRIMARY文件组,收缩PRIMARY完全没用
执行前不检查这三件事,大概率锁死业务或写满磁盘
重建操作本身会短暂占用双倍磁盘空间,且 purge 被阻塞时,即使跑完,实际释放量也可能远低于预期。
-
df -h /var/lib/mysql,确认剩余空间 ≥SHOW TABLE STATUS LIKE 't'\G中的Data_length× 1.5(临时表 + 日志要落脚) - 查长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 600,有结果就得先KILL - 确认
innodb_file_per_table = ON(MySQL)或目标表归属正确的文件组(SQL Server),否则重建无效
最常被忽略的是:Data_free 高 ≠ 必须优化——它本就是为写入性能预留的缓冲区;真正该盯的是磁盘是否告警、业务是否因空间不足开始报错。


















