DELETE不释放磁盘空间,因InnoDB仅标记删除行可复用而不归还OS;需OPTIMIZE TABLE(锁表)、TRUNCATE TABLE或pt-online-schema-change等方案回收空间。

DELETE后磁盘空间没变?InnoDB不会自动回收数据文件
MySQL用DELETE删掉几百万行,df -h一看磁盘占用纹丝不动——这不是bug,是InnoDB的设计使然。它把删除标记为“可复用”,但不归还给操作系统,数据文件(ibdata1或独立表空间.ibd)体积维持原样。
常见错误现象:SELECT COUNT(*)返回0,但du -sh *.ibd发现文件大小和删前几乎一致;SHOW TABLE STATUS里Data_length没降,Free_space可能反而变小(碎片加剧)。
- 只有使用
TRUNCATE TABLE才能清空并重置表空间(但会重建表,且不可回滚) -
OPTIMIZE TABLE可重建表、整理碎片、释放空间——但它会加全表锁,线上慎用 - 5.6+支持
ALTER TABLE ... ENGINE=InnoDB达到类似效果,但同样需要锁表和临时空间 - 如果启用了
innodb_file_per_table=ON(推荐),释放的空间只在该表的.ibd文件内可复用;否则所有表共用ibdata1,删了也白删——永远不缩
为什么OPTIMIZE TABLE有时也不缩容?看准free_space和file-per-table
OPTIMIZE TABLE不是万能药。它本质是CREATE + INSERT + DROP三步,最终生成一个紧凑的新.ibd,但能否真正缩小文件,取决于当前有多少真实空闲页可被合并。
使用场景:表经历过大量随机DELETE/UPDATE,SHOW TABLE STATUS中Data_free值很大(比如超1GB),且确认innodb_file_per_table=ON。
- 执行前先查:
SELECT table_name, data_length, data_free FROM information_schema.tables WHERE table_schema='your_db' AND table_name='your_table'; -
data_free > 0只是有空闲页,不代表能压缩;若表本身已很紧凑(如刚INSERT完就DELETE),OPTIMIZE后文件大小可能不变 - 5.7+支持
ALGORITHM=INPLACE的OPTIMIZE(需满足条件),但仍会短暂锁DDL;8.0默认用INPLACE,但对大表仍耗时 - 别在从库上直接跑
OPTIMIZE——它不走binlog,主从空间状态会不一致
想在线释放空间?考虑ROW_FORMAT和pt-online-schema-change
业务不能停,又想让.ibd变小,纯SQL方案基本无解。InnoDB没有“在线收缩数据文件”的能力,必须借助外部工具或架构调整。
参数差异:ROW_FORMAT=COMPRESSED或ROW_FORMAT=DYNAMIC本身不缩小现有文件,但影响后续插入的存储密度;压缩表(KEY_BLOCK_SIZE)需配合innodb_file_per_table=ON和innodb_file_format=Barracuda(5.7已废弃,8.0默认支持)。
- 真正可行的在线方案:用
pt-online-schema-change重建表(底层仍是CREATE+COPY+SWAP),支持限流、校验、失败回滚 - 注意
pt-osc会在原表上建触发器,高QPS下可能拖慢DML;务必提前测试触发器开销 - 如果磁盘实在吃紧,可临时把旧
.ibd移到其他挂载点,再ALTER TABLE ... DISCARD TABLESPACE+IMPORT TABLESPACE(要求严格一致的表结构和物理备份)
误删后恢复空间,别只盯着DELETE和OPTIMIZE
很多同学删完数据立刻OPTIMIZE,结果发现更卡、从库延迟暴增——其实问题常出在没关掉autocommit或没控制事务粒度。
性能影响:单个大事务DELETE FROM t WHERE ...会撑爆undo log,阻塞purge线程,导致show engine innodb status里看到大量history list length堆积,进而拖慢所有DML。
- 批量删除务必分批,用
WHERE id BETWEEN x AND y+LIMIT 10000,每次提交 - 删完别急着
OPTIMIZE,先等innodb_purge_threads把undo清理干净(观察history list length降到百以内) - 如果表长期只增不删,考虑按时间分区(
PARTITION BY RANGE),删整月数据只需DROP PARTITION,瞬间释放空间
最常被忽略的一点:innodb_file_per_table必须在建表前开启。运行中改这个变量只影响新表,老表的.ibd永远无法拆出ibdata1——这时候想缩容,只剩导出导入一条路。










