delete不释放物理空间,因其仅为逻辑删除:innodb设delete_mask位并入purge队列,sql server打删除标记或更新slot数组,数据页仍保留在文件中,需optimize table、alter index rebuild等显式操作才能释放。

DELETE 不释放物理空间,是因为它根本就不是物理删除操作——InnoDB 和 SQL Server 都只做逻辑标记,不归还页给操作系统。
DELETE 只是翻个牌,不是扔掉牌
- InnoDB 把行头的
delete_mask位设为 1,并把该行加入 purge 队列;数据页本身仍保留在.ibd文件里,供后续INSERT复用 - SQL Server 堆表对每行打“已删除”标记,聚集表则在页内更新 slot 数组,但整页归属不变
- 这导致
SHOW TABLE STATUS的Data_length几乎不变,sys.dm_db_file_space_usage的unallocated_extent_page_count也不降 - 即使
SELECT COUNT(*) = 0,du -sh table.ibd或sp_spaceused仍显示原大小
这不是 bug,是设计取舍:用空间换 MVCC 正确性、避免频繁文件截断带来的写放大和锁争用。
真正释放物理空间必须重建或收缩
- MySQL 必须走重建路径:
OPTIMIZE TABLE t或ALTER TABLE t ENGINE=InnoDB(二者在 5.7+ 效果一致) - SQL Server 堆表加
TABLOCK提示后DELETE可释放页(仅限未启用READ_COMMITTED_SNAPSHOT的库);聚集表优先用ALTER INDEX ALL ON t REBUILD -
TRUNCATE TABLE能立刻释放,但不支持WHERE,且重置自增、需更高权限 -
DBCC SHRINKFILE是最后手段:它移动页、制造碎片、可能阻塞快照读,且只对目标文件生效
注意:DBCC SHRINKDATABASE 不推荐——它遍历所有文件,副作用比单文件收缩更严重。
执行前不检查这三件事,大概率失败
- 确认
innodb_file_per_table = ON(MySQL)或目标表在可收缩的文件组中(SQL Server),否则重建无效 - 查长事务:
SELECT * FROM information_schema.INNODB_TRX WHERE TIME_TO_SEC(NOW() - trx_started) > 600,有结果必须先KILL - 磁盘剩余空间 ≥ 当前表
Data_length× 1.2(临时表 + redo/binlog 要落盘)
RDS/PolarDB 等托管服务常禁用 OPTIMIZE TABLE,或自动转到只读副本执行,得先查控制台文档。
最易被忽略的是:删完不等于空了,空了也不等于 OS 看得见空。物理释放永远需要一次显式重建或收缩动作,没有任何后台线程会自动帮你把 .ibd 或 .mdf 文件切小。










