DELETE后data_length不变是正常设计,因InnoDB仅标记删除而不物理释放空间,需OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB重建表才能真正回收磁盘空间。

MySQL 中 DELETE 后 data_length 不变是正常行为
这不是 bug,而是 InnoDB 的设计选择:被删的行只是打上“已删除”标记(marked for deletion),所在的数据页也不会立即回收,仍保留在表空间文件里等待复用。所以 SHOW TABLE STATUS 查到的 data_length 基本不变,data_free 可能变大——这代表“可复用但未归还 OS”的空间。
常见错误现象包括:
- 监控显示磁盘使用率居高不下,即使已
DELETE FROM t WHERE ...清掉 80% 数据 -
du -sh /var/lib/mysql/dbname/t.ibd文件大小毫无变化 - 误以为
DELETE和TRUNCATE效果等价
注意:TRUNCATE TABLE 或 DROP TABLE 才会立刻释放文件级空间;DELETE 永远不直接缩文件。
用 OPTIMIZE TABLE 回收 InnoDB 表空间的实际代价
它本质是重建表:创建新临时表 → 拷贝有效行 → 删除原表 → 重命名。这个过程会释放碎片、重排聚簇索引、重置 data_free 到接近 0。
但必须正视三个硬约束:
- 执行期间需持有
S(共享)元数据锁,阻塞写入;结束前还要抢一次X锁做原子切换,高峰期易卡住 - 临时表 + 新索引会占用额外磁盘空间,建议操作前确保空闲空间 ≥ 当前表
data_length的 1.2 倍 - 在 RDS 或 PolarDB 等托管服务中,
OPTIMIZE TABLE可能被禁用或转为只读副本上执行,需查控制台文档确认支持性
替代写法(效果相同但更透明):ALTER TABLE t ENGINE=InnoDB,同样触发重建,且明确表达意图。
PostgreSQL 里对应的是 VACUUM 而非 VACUUM FULL
VACUUM 是常规维护命令,它清理 dead tuple 并把空间标记为“可复用”,但不会把空间还给操作系统——文件大小不变,只是后续 INSERT 能复用这些页。
只有 VACUUM FULL t 才真正收缩物理文件,但它会锁表、阻塞所有读写,且全程单线程,大表可能耗时数小时。生产环境更推荐:
- 定期跑轻量
VACUUM(配合autovacuum参数调优) - 对长期只删不插的归档表,再择机
VACUUM FULL - 避免在事务中执行
VACUUM FULL,它不能在事务块内运行
错误认知:“VACUUM 就等于释放磁盘”——其实它默认不碰文件系统层。
Linux 文件被进程占用导致 rm 后空间不释放
这和数据库无关,但现象高度相似:你 rm /var/log/app.log,df -h 却没变化。根本原因是该文件仍被某个进程以 open() 方式持有(比如日志轮转未 reload,或后台服务持续写入)。
验证方法只有一条:lsof +L1 或 lsof | grep deleted。输出里带 (deleted) 的行就是真凶。
解决路径很窄:
- 重启对应进程(如
systemctl restart nginx) - 若无法重启,可向进程发
SIGHUP促使其重新打开日志文件(前提是程序支持) - 切勿暴力
kill -9,可能丢数据
这个场景最容易和 MySQL 的 DELETE 空间问题混淆,但根源完全不同:一个是逻辑标记,一个是文件句柄未关闭。

















