先清理碎片再迁移可显著减少传输体积和导入耗时,但需精准判断碎片率(frag_pct≥20%才处理)、用ALTER TABLE ENGINE=InnoDB替代OPTIMIZE、配合导出参数优化及导入前禁用校验,超大表应分块归档而非硬清理。

直接清理碎片再迁移,能显著减少传输体积和导入耗时——但不是所有“清理”都有效,关键在操作方式和时机。
先确认碎片是否真实存在且值得动
别一上来就 OPTIMIZE TABLE。InnoDB 表的 DATA_FREE 值大于 0 不代表必须整理;真正影响迁移的是 .ibd 文件物理大小与实际数据量的比值。用这条 SQL 查:
SELECT TABLE_NAME, ROUND(DATA_LENGTH/1024/1024, 2) AS data_mb,
ROUND(DATA_FREE/1024/1024, 2) AS free_mb,
ROUND((DATA_FREE / (DATA_LENGTH + DATA_FREE)) * 100, 2) AS frag_pct
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'your_db' AND ENGINE = 'InnoDB';- 如果
frag_pct - 若 > 20% 且表是
innodb_file_per_table = ON(绝大多数现代部署),才值得继续 - 注意:分区表不能靠
OPTIMIZE清理全局碎片,得用ALTER TABLE ... DROP PARTITION
用 ALTER TABLE ENGINE=InnoDB 替代 OPTIMIZE TABLE
OPTIMIZE TABLE 对 InnoDB 实际执行的就是 ALTER TABLE ... ENGINE=InnoDB,但前者会隐式加读锁、触发统计信息重采样,且无法控制排序方式。手动执行后者更可控:
ALTER TABLE your_large_table ENGINE=InnoDB ROW_FORMAT=DYNAMIC;
- 显式指定
ROW_FORMAT(推荐DYNAMIC)可避免因旧格式导致的额外空间浪费 - 该操作会重建聚簇索引,把散落的数据页重新紧凑排列,
.ibd文件体积通常下降 30%–60% - 它会锁表(非只读),但比
OPTIMIZE更快完成,且不触发ANALYZE TABLE额外开销 - 务必在低峰期执行,大表(>50GB)建议配合
pt-online-schema-change避免锁表
迁移前关掉 binlog 和唯一性检查,但别关错地方
碎片清理后导出,必须同步调整导出命令参数,否则压缩效果白费:
- 导出时加
--skip-extended-insert:生成单条多行 INSERT,减少 SQL 解析压力 - 导出时加
--hex-blob:二进制字段不转义成字符串,体积小、解析快 - 导入前在目标库执行:
SET FOREIGN_KEY_CHECKS=0; SET UNIQUE_CHECKS=0; SET sql_log_bin=0; - ⚠️ 注意:
sql_log_bin=0只对当前会话生效,且主从场景下这批数据不会复制到从库,需人工补位 - 导入完成后立刻恢复:
SET FOREIGN_KEY_CHECKS=1; SET UNIQUE_CHECKS=1;
超大表(>100GB)别硬清碎片,改用分块归档+重建
直接 ALTER TABLE ... ENGINE=InnoDB 容易 OOM 或超时,此时碎片清理应让位于迁移本身:
- 按时间字段分批导出近 30 天活跃数据:
mysqldump --where="created_at >= '2026-06-01'" - 历史数据迁入归档库或冷表,用
INSERT INTO ... SELECT+TRUNCATE替代 DELETE - 清空原表后,用
CREATE TABLE new_table LIKE old_table新建空表,再导入活跃数据 - 这样既释放了空间,又绕过了碎片整理的高风险阶段,导入速度提升更稳定
真正容易被忽略的是:碎片清理后的文件体积缩减,并不自动反映在 mysqldump 输出中——你必须用 --skip-extended-insert 和 --hex-blob 才能把物理层的紧凑性转化为传输层的实际收益。否则,.ibd 小了 50%,dump.sql 还是那么大。


















