真正能缩容innodb表空间的只有重建或迁移:optimize table对共享表空间ibdata1无效,对独立表空间.ibd也仅在特定条件下回收空间;需通过逐表重建+清理undo或全库dump重配实现。

InnoDB 表空间膨胀后磁盘不释放,不是 OPTIMIZE TABLE 能解决的——它对共享表空间 ibdata1 完全无效,对独立表空间 .ibd 也只在特定条件下回收空间;真正能缩容的,只有重建或迁移。
为什么 OPTIMIZE TABLE 和 ALTER TABLE ENGINE=InnoDB 常常没用
这两个操作看似“整理”,但实际效果高度依赖表空间模式和数据分布:
-
OPTIMIZE TABLE在innodb_file_per_table = OFF时,只在ibdata1内部重组页,绝不释放文件系统空间;即使设为 ON,若表有大 BLOB/TEXT 字段或页碎片严重,也可能只回收少量空间 -
ALTER TABLE tbl_name ENGINE=InnoDB对已启用独立表空间的表,会重建并写入新.ibd,但旧文件不会立即删除——需等事务提交、 purge 完成、且没有活跃读视图(read view)引用旧页,才由 InnoDB 异步清理 - 如果表上存在长事务(哪怕只读),其
trx_state = 'RUNNING'且trx_query IS NULL,就会钉住 undo 和历史页,导致新.ibd写完后旧空间仍被锁住无法回收
如何确认是 ibdata1 共享表空间撑爆了
这是最隐蔽也最难缩容的情况:删库删表、OPTIMIZE、甚至 DROP DATABASE 都不减 ibdata1 大小。
- 执行
SELECT @@innodb_file_per_table;—— 返回0就是关着的,所有表都挤在ibdata1里 - 对比大小:
ls -lh /var/lib/mysql/ibdata1vsSELECT SUM(data_length + index_length) FROM information_schema.tables WHERE engine='InnoDB';;若前者远大于后者(比如 50G vs 5G),基本锁定问题 - 查
information_schema.INNODB_SYS_TABLES:SELECT table_name, file_format FROM information_schema.innodb_sys_tables WHERE name LIKE 'your_db/%';;若结果中无.ibd路径对应项,说明表未落在独立文件
真正有效的缩容路径只有两条
别指望在线收缩——InnoDB 不支持 ibdata1 或独立 undo 表空间的 online shrink。
-
路径一(推荐,适用于已启用独立表空间的实例):逐表重建 + 清理 undo
确保innodb_undo_tablespaces >= 2,杀掉所有trx_state = 'RUNNING'且trx_query IS NULL的长事务 →SET GLOBAL innodb_undo_log_truncate = ON→ 对每个 undo 表空间执行ALTER UNDO TABLESPACE undo_001 TRUNCATE -
路径二(必须停机,适用于共享表空间顽疾):全库 dump + 清空 + 重配 + 导入
停 MySQL → 备份(mysqldump --all-databases --no-tablespaces)→ 删除ibdata1、ib_logfile*、ibtmp1→ 启用innodb_file_per_table = ON后启动 → 导入 → 对所有业务表执行ALTER TABLE tbl_name ENGINE=InnoDB;强制落到.ibd
容易被忽略的关键点
重建或 truncate 后空间仍不下降?大概率是 purge 线程没跟上,或还有残留长事务。
- 执行
SHOW ENGINE INNODB STATUS\G,紧盯HISTORY LIST LENGTH—— 杀完事务后它应持续下降;若卡住不动,检查innodb_purge_threads是否为 1,或磁盘 IO 是否饱和 -
innodb_undo_log_truncate = ON不是开关,它只在HISTORY LIST LENGTH 且无活跃事务引用对应 undo 页时才触发裁剪 - 迁移后新表自动走独立空间,但老表不会自动迁移;导入 dump 后那一步
ALTER TABLE ... ENGINE=InnoDB必须执行,否则仍可能写回ibdata1











