根本原因是DELETE仅标记行为空闲而不回收数据页,表文件大小不变;OPTIMIZE TABLE通过重建表释放空间并整理碎片,但需锁表和额外磁盘空间;分区表DROP PARTITION是更优的在线清理方案。

MySQL大表DELETE后磁盘空间不释放,根本原因是什么
因为DELETE只是标记数据行为空闲(InnoDB中是把记录加删除位、放入undo log),并不真正回收数据页,更不会归还给操作系统。表文件(.ibd)大小几乎不变,SELECT COUNT(*)变小了,但du -sh table.ibd纹丝不动。
常见误操作是反复DELETE FROM t WHERE ...清理历史数据,结果磁盘告警越来越频繁——这不是“删得不够”,而是“删完没整理”。
OPTIMIZE TABLE对InnoDB表到底做了什么
它本质是重建表:创建新临时表 → 拷贝有效行(跳过已删除标记的)→ 删除原表 → 重命名新表 → 更新统计信息。过程中会真正释放空闲页,合并碎片,并重置auto_increment计数器(如果表有该字段)。
注意:OPTIMIZE TABLE在MySQL 5.6+对InnoDB等价于ALTER TABLE ... FORCE(或ALGORITHM=INPLACE不可用时退化为COPY),不是轻量操作:
面向 CRM 系统的 MySQL 8 数据库模式设计。当用户需要为客户关系管理(CRM)系统设计、评审、优化或生成数据库模式时,应使用此项技能。
- 全程锁表(
LOCK=EXCLUSIVE),写入阻塞,读可能被阻塞(取决于隔离级别和版本) - 需要约2倍磁盘空间(原表 + 新表并存期间)
- 执行时间与有效数据量正相关,不是与删除量相关
- MySQL 8.0.29+支持
ALGORITHM=INPLACE, LOCK=NONE的OPTIMIZE TABLE,但仅限于某些场景(如仅BLOB列变更),大表仍大概率走COPY路径
替代方案:为什么TRUNCATE不行、PARTITION更靠谱
TRUNCATE TABLE虽快且释放空间,但它清空整表,无法按条件删除旧数据——不符合“大表删部分”的前提。
真正适合线上大表定期归档/清理的,是分区表(PARTITION BY RANGE (created_at)):
- 用
ALTER TABLE t DROP PARTITION p202301秒级删除整个月数据,空间立即返还操作系统 - 无需拷贝数据,不锁全表(仅锁对应分区)
- 前提是建表时就规划好分区策略;事后添加分区需
REORGANIZE,仍涉及数据移动 - 注意
innodb_file_per_table=ON必须开启,否则分区数据仍混在ibdata1里,删了也白删
执行OPTIMIZE前必须确认的三件事
别急着敲命令,先检查这三项,否则可能引发超长锁表甚至OOM:
- 确认
innodb_file_per_table=ON(SHOW VARIABLES LIKE 'innodb_file_per_table'),否则OPTIMIZE对独立表文件无效 - 检查剩余磁盘空间 ≥ 当前
.ibd文件大小 × 1.2,避免重建中途失败卡死 - 评估业务低峰期窗口:用
SELECT COUNT(*) FROM t WHERE [your condition]预估有效行占比;若只剩10%数据,OPTIMIZE耗时可能接近重建一张新表
碎片本身不影响单行查询性能,但高水位下的全表扫描、范围查询会变慢;而空间不释放是运维层面的硬瓶颈。这两者要分开判断——不是所有“删了没缩容”都值得立刻OPTIMIZE。

















