delete只是标记删除而非物理擦除,innodb设delete_mask位、sql server打页内标记,均不归还空间给操作系统;真正释放需mysql用optimize table重建表或sql server用alter index rebuild/dbcc shrinkfile。

DELETE只是标记删除,不是物理擦除
执行 DELETE 后磁盘空间没变小,不是你写错了 SQL,也不是数据库坏了——这是 InnoDB 和 SQL Server 的默认行为。InnoDB 把行头的 delete_mask 设为 1,SQL Server 则在页里打“已删除”标记,两者都不动物理页位置,也不通知操作系统归还空间。
被删的数据页仍属于 .ibd 或 .mdf 文件,后续 INSERT 会优先复用它们。所以 SHOW TABLE STATUS 的 Data_length、sys.dm_db_file_space_usage 的 unallocated_extent_page_count 都不会明显下降。
- 即使
SELECT COUNT(*) = 0,文件尺寸也纹丝不动 -
Data_free变大 ≠ 空间已释放给 OS,它只是“内部可复用但未归还”的缓冲区 - 启用
READ_COMMITTED_SNAPSHOT(SQL Server)或长事务(MySQL)会让 purge 线程卡住,已删数据滞留更久
真正释放空间必须重建表或收缩文件
想让 .ibd 或数据库文件物理变小,得绕过“标记删除”机制,走重建路径:
- MySQL:用
OPTIMIZE TABLE t或ALTER TABLE t ENGINE=InnoDB—— 效果相同,都是新建空表、拷贝有效行、原子替换旧文件 - SQL Server:堆表加
TABLOCK提示后DELETE可释放页;聚集表优先用ALTER INDEX ALL ON t REBUILD;仅当磁盘告警时才考虑DBCC SHRINKFILE(非SHRINKDATABASE) - TRUNCATE 或 DROP 能立刻释放,但不支持 WHERE 条件,且重置自增、破坏外键依赖
注意:OPTIMIZE TABLE 在 MySQL 5.7+ 等价于 ALTER TABLE t FORCE,仍需两次元数据锁;DBCC SHRINKFILE 会引发严重索引碎片,收缩后必须跟 REBUILD。
执行前不检查这三件事,大概率失败
盲目运行重建命令,轻则锁表数小时,重则写满磁盘、阻塞全库 DDL:
- 磁盘剩余空间 ≥ 表当前
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),否则重建无效
特别容易忽略的是:RDS/PolarDB 等托管服务常禁用 OPTIMIZE TABLE,或自动转到只读副本执行,得先查控制台文档。
VACUUM 和 SHRINK 不等于“释放给操作系统”
PostgreSQL 的 VACUUM、SQL Server 的 DBCC SHRINKDATABASE、MySQL 的 OPTIMIZE TABLE 名字相似,但层级不同:
-
VACUUM(无 FULL)只清理 dead tuple 并标记页为可复用,.dat文件大小不变 -
DBCC SHRINKDATABASE移动页并截断文件尾部,但会炸碎聚集索引,且不跨文件组生效 -
OPTIMIZE TABLE是唯一真正丢弃旧页、让 OS 回收空间的操作,但它代价最高
真正关键的判断点从来不是“Data_free 多高”,而是“是否真需要把空间还给操作系统”。多数生产场景只需内部复用——那根本不用动,等后续 INSERT 自然填满空页就行。











