迁移后表空间碎片未释放的根源是目标实例innodb_file_per_table=OFF,导致所有表写入共享表空间ibdata1,此时OPTIMIZE TABLE或ALTER TABLE ENGINE=InnoDB均无法将空间返还操作系统,必须停机导出、初始化新实例并显式配置innodb_file_per_table=ON后导入。

迁移后表碎片没释放,不是迁移操作本身的问题,而是目标实例的 innodb_file_per_table 很可能关着——这是最常见、最致命的根源。
先确认 innodb_file_per_table 是否为 ON
迁移常走 mysqldump 或物理拷贝,但不会自动继承源库的配置。如果目标 MySQL 的 innodb_file_per_table 是 OFF,所有表都写进共享表空间 ibdata1,无论你执行多少次 OPTIMIZE TABLE 或 ALTER TABLE ENGINE=InnoDB,磁盘空间都收不回来。
- 执行
SHOW VARIABLES LIKE 'innodb_file_per_table';,返回OFF就必须停机处理 - 不能在线开启:该参数是只读的,修改后需重启 MySQL 才生效
- 已上线系统无法“热修复”,必须导出 → 初始化新实例(
my.cnf显式配innodb_file_per_table=ON)→ 导入 - 切勿尝试删除
ibdata1后重启,MySQL 会直接启动失败
DATA_FREE 很大但 .ibd 文件不缩?检查三个硬性前提
DATA_FREE 高 ≠ 一定能回收磁盘空间。它只是 InnoDB 内部可复用的空闲字节数,是否能还给操作系统,取决于:
-
innodb_file_per_table=ON:已确认为前提,否则跳过后续判断 - 无长事务阻塞:执行
SHOW PROCESSLIST;,看是否有状态为Waiting for table flush或Waiting for table metadata lock的线程;有则需 kill 或等其结束 - 磁盘临时空间充足:重建过程需额外空间 ≥ 当前
data_length + index_length;若只剩 15GB,而表占 20GB,命令会卡住或报错ERROR 1114 (HY000): The table is full - 别被
ROW_FORMAT坑了:原表是COMPRESSED,MySQL 5.7 某些小版本对它静默跳过OPTIMIZE TABLE,不报错也不生效
别用 OPTIMIZE TABLE,改用 ALTER TABLE ENGINE=InnoDB
OPTIMIZE TABLE 在 InnoDB 中就是 ALTER TABLE t ENGINE=InnoDB 的语法糖,但前者容易让人误以为“轻量优化”,实际行为完全一样:锁表、重建、耗空间。换成后者语义更清晰,协作时不易误解。
- 基础写法:
ALTER TABLE your_table ENGINE=InnoDB; - 加
ALGORITHM=INPLACE没用:该参数对碎片整理无效,InnoDB 碎片回收必须全量重建 - 大表建议搭配
LOCK=NONE(仅 MySQL 8.0+ 支持),或用pt-online-schema-change --alter "ENGINE=InnoDB"实现低影响重建 - 重建前可调大会话级排序缓冲:
SET SESSION sort_buffer_size = 268435456;(256MB),加快数据重排阶段
碎片是否真该处理?别只看 DATA_FREE
DATA_FREE > 0 不等于必须优化。真正值得干预的信号是:
-
DATA_FREE / (data_length + index_length) > 5%或绝对值 > 1GB - 平均行长度远大于字段实际占用(比如 VARCHAR(255) 存的全是短字符串,但
Avg_row_length显示 1.8KB) - 历史
DELETE比例长期 > 30%,且后续几乎无 INSERT/UPDATE 回填 - 慢查询频繁出现
Using temporary; Using filesort,且EXPLAIN中rows远大于filtered * rows,说明 B+ 树深度失衡或页内空洞拖慢扫描
最易被忽略的一点:即使 DATA_FREE 很高,只要后续 INSERT 能稳定复用这些空洞,物理文件不缩也没关系——InnoDB 的设计本就倾向“空间复用优先于立即归还”。强行重建反而带来锁表风险和临时空间压力。


















