DELETE不释放磁盘空间是InnoDB正常设计,需满足innodb_file_per_table=ON等条件并执行OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB才能真正缩表;碎片率超25%且DATA_FREE>0时才建议操作。

DELETE 后磁盘空间不释放是正常现象
MySQL 的 DELETE 只是标记行删除,不立即回收磁盘空间。尤其是 InnoDB 表,数据页里留下的空洞(free space)会被后续 INSERT 复用,但文件大小不会自动缩小——ibd 文件体积保持不变。
常见错误现象:SELECT COUNT(*) 返回 0,但 du -h table.ibd 显示文件仍占几十 GB;监控看到磁盘使用率没降。
- 这是 InnoDB 的设计使然,不是 bug,也不是配置错了
- MyISAM 表同样存在类似行为,但处理机制不同(不涉及 MVCC 和页内碎片)
- 如果表启用了
innodb_file_per_table=OFF,空间根本不会释放到文件系统,而是留在共享表空间ibdata1中,此时OPTIMIZE TABLE也无效
OPTIMIZE TABLE 真正起作用的条件
OPTIMIZE TABLE 对 InnoDB 来说本质是重建表:创建新临时表 → 拷贝有效数据 → 删除原表 → 重命名。只有满足这些条件,才能真正缩表、释放磁盘空间。
- 必须开启
innodb_file_per_table=ON(5.6.6+ 默认开启,但老实例常被手动关掉) - 表不能被其他长事务或锁阻塞,否则会卡在“Waiting for table flush”
- 执行期间表会被加
EXCLUSIVE元数据锁,DML 阻塞,线上慎用 - 如果表有全文索引(
FULLTEXT),OPTIMIZE TABLE会重建它,耗时显著增加
示例命令:OPTIMIZE TABLE user_orders; —— 执行完后可对比 ls -lh user_orders.ibd 前后大小。
替代方案:ALTER TABLE ... ENGINE=InnoDB 更可控
和 OPTIMIZE TABLE 效果一致,但语义更明确,且支持更多控制选项,比如指定排序列减少后续查询碎片。
- 等价于
ALTER TABLE t ENGINE=InnoDB;,会触发重建 - 可以加
ORDER BY,例如:ALTER TABLE logs ENGINE=InnoDB ORDER BY created_at;,让物理存储更有序,提升范围查询性能 - 在 MySQL 5.7+ 中,若启用
innodb_fast_alter_table(默认开),这种操作不锁表(仅短暂元数据锁),但依然要预留足够磁盘空间存临时表 - 注意:如果表有外键引用,需先禁用
FOREIGN_KEY_CHECKS=0,否则报错Cannot truncate a table referenced in a foreign key constraint
碎片率怎么判断?别只看感觉
光看行数减少就执行 OPTIMIZE 很危险——小表碎片影响微乎其微,大表盲目操作可能引发小时级锁表和磁盘爆满。
- 查碎片率用:
SELECT DATA_LENGTH, DATA_FREE, ROUND(DATA_FREE/DATA_LENGTH, 2) AS frag_ratio FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='db_name' AND TABLE_NAME='t'; -
DATA_FREE > 0且frag_ratio > 0.25(25%)才值得考虑整理 -
DATA_FREE是“已分配但未使用的空间”,对innodb_file_per_table=OFF的表,这个值恒为 0,别被误导 - 生产环境建议搭配
pt-online-schema-change在线操作,但要注意它依赖触发器,高并发写入下可能堆积延迟
最常被忽略的一点:OPTIMIZE 或 ALTER 重建后,如果没有及时更新统计信息,优化器可能继续走坏的执行计划——记得手动跑 ANALYZE TABLE。










