optimize table能释放磁盘空间,是因为它重建整张表:删旧.ibd文件、写新.ibd文件、原子替换,绕过innodb标记删除机制,仅拷贝当前可见行,使操作系统级文件大小回落;若innodb_file_per_table=off,则无法释放空间。

OPTIMIZE TABLE 能释放磁盘空间,是因为它根本不是“重建索引”,而是重建整张表——删旧文件、写新文件、原子替换,物理上抹掉所有已删除行和空洞。
OPTIMIZE TABLE 实际执行的是 ALTER TABLE ENGINE=InnoDB
MySQL 5.6+ 中,对 InnoDB 表运行 OPTIMIZE TABLE t,内部等价于:ALTER TABLE t FORCE 或 ALTER TABLE t ENGINE=InnoDB。它不单独重建索引,而是:创建空表结构 → 扫描原表所有未被 purge 的有效行 → 按当前 innodb_page_size 和 ROW_FORMAT 重新分配页 → 写入新 .ibd 文件 → 原子切换文件句柄 → 删除旧 .ibd。
这个过程绕过了 InnoDB 的“标记删除”机制,自然清空了 Data_free,也让操作系统级的文件大小回落到真实数据+索引占用水平。
- 不是“整理碎片”,是“换一套房子住”
- 若原表在系统表空间(
ibdata1),哪怕执行成功,磁盘空间也不会变小 - MySQL 5.7+ 自动附加
ANALYZE,统计信息会更新,但这是附带效果,不是空间回收原因
为什么 DELETE 后空间不释放,而 OPTIMIZE 可以?
DELETE FROM t WHERE ... 只是把行标记为删除,并把对应页加入 undo log 和 purge 队列;这些页仍保留在 .ibd 文件中,供后续 INSERT 复用。InnoDB 默认不把空闲页还给操作系统——这是设计,不是 bug。
OPTIMIZE TABLE 则彻底跳过这套复用逻辑:它只拷贝“当前可见、未被标记删除”的行,不带任何历史空洞,新文件从零开始写,旧文件被 unlink(),内核才真正回收磁盘块。
- 所以
DELETE+OPTIMIZE是两步缺一不可的组合 - 单纯
ANALYZE TABLE或REPAIR TABLE对 InnoDB 无效,也不影响磁盘大小 - 如果
purge线程卡住(比如有长事务),OPTIMIZE仍能回收空间,因为它不依赖 purge 清理结果
哪些条件不满足,OPTIMIZE 就白跑?
常见“执行了但 .ibd 没变小”的根本原因,基本都落在这三点:
-
innodb_file_per_table是 OFF:查SELECT @@innodb_file_per_table;,返回 0 就说明所有表共用ibdata1,OPTIMIZE无法释放文件系统空间 - 磁盘临时空间不足:重建过程需 ≈ 当前
.ibd大小的额外空间;若只剩 30GB,而表是 45GB,命令可能静默失败或卡在Waiting for table flush - 表不在独立表空间:用
SELECT FILE_NAME FROM INFORMATION_SCHEMA.INNODB_TABLESPACES WHERE NAME = 'db_name/table_name';确认路径是否指向.ibd;分区表、系统表、加密表等可能不支持
执行后务必验证:ls -lh /var/lib/mysql/db_name/tbl_name.ibd 对比前后大小,再查 SHOW TABLE STATUS LIKE 'tbl_name'\G 看 Data_free 是否归零——别只信 “OK” 返回。
大表执行时最易被忽略的细节
大表(>50GB)跑 OPTIMIZE TABLE 不是“慢一点”,而是极易引发连锁故障:临时空间吃满、主从延迟爆炸、buffer pool 雪崩式刷脏、甚至触发 OOM killer 杀 mysqld 进程。
它不区分“冷热数据”,全量扫描、全量重写、全量 redo,IO 峰值毫无缓冲。线上环境除非确认磁盘余量 > 表大小 × 2、无活跃长事务、且业务可容忍数小时锁表,否则真不该直接上。











