根本原因是分批迁移时事务未及时提交,导致隐式长事务持续持有ReadView,使旧版本数据无法被purge清理;应改用主键范围切片+显式COMMIT,每批独立事务,并实时监控INNODB_TRX和History list length。

迁移千万级大表时 undo log 暴涨,根本不是“导数据太慢”导致的,而是分批逻辑写法不当、事务未及时提交,把迁移过程变成了一个隐式长事务——哪怕你没显式 BEGIN,每一批没 COMMIT 就继续下一批,InnoDB 就会持续持有 ReadView,所有旧版本都钉死在 undo 里。
为什么用 LIMIT OFFSET 分页迁移会卡住 purge
常见错误写法是:INSERT INTO new_table SELECT * FROM old_table ORDER BY id LIMIT 10000 OFFSET 0,然后 OFFSET 加 10000 往下滚。问题在于:
- MySQL 5.7/8.0 默认 autocommit=1,但一旦显式
START TRANSACTION或执行了第一个INSERT后没COMMIT,后续所有语句都在同一个事务里 -
LIMIT OFFSET在大偏移下性能差,且容易漏行/重复,更关键的是——OFFSET 越大,扫描行数越多,事务内累积的trx_rows_modified越高,undo 消耗呈指数增长 - 如果迁移脚本崩溃或被 kill,这个事务就变成
trx_state = 'RUNNING'且trx_query IS NULL的幽灵连接,直接卡死 purge
必须用主键范围 + 显式 COMMIT 控制事务边界
正确做法是按主键(最好是自增 id)切片,每批独立事务:
- 查出最小和最大
id:SELECT MIN(id), MAX(id) FROM large_table - 每次取一段连续区间,例如:
SELECT * FROM large_table WHERE id BETWEEN 100000 AND 110000 ORDER BY id - 插入目标表后立刻
COMMIT;不要依赖 autocommit,显式写出来更安全 - 批次大小建议 1万~5万行,具体看单行体积和网络延迟;超过 10 万行的批次要警惕
trx_rows_modified > 10000触发 undo 压力阈值
迁移中如何实时监控 undo 是否被拖垮
别等磁盘告警才反应——迁移开始后每 2 分钟执行一次检查:
- 查最老事务:
SELECT trx_id, trx_started, trx_state, trx_rows_modified FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 3,重点盯trx_started超过 600 秒且trx_query IS NULL的记录 - 看 purge 进度:
SHOW ENGINE INNODB STATUS\G,搜PURGE DONE for trx's n:o,数字是否在缓慢递增;如果不涨,说明 purge 已被卡住 - 查 history list:
SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_history_list_length',持续 > 50000 就得干预 - 若发现异常事务,先用
trx_mysql_thread_id关联information_schema.PROCESSLIST确认来源,再按状态决定是否KILL
迁移完成但 undo 文件仍不收缩?别删文件
即使数据迁完了,undo 文件物理尺寸也不会自动变小——ALTER UNDO TABLESPACE undo_001 TRUNCATE 不是“删文件”,而是标记空间可复用。但这个操作有前提:
- 必须至少有 2 个 active undo 表空间(
SHOW VARIABLES LIKE 'innodb_undo_tablespaces'≥ 2),否则会报错ERROR 3655 (HY000): Cannot set innodb_undo_001 inactive... - 必须先
CREATE UNDO TABLESPACE undo_003 ADD DATAFILE '/var/lib/mysql/undo_003.ibu'(注意后缀必须是.ibu) - 再
ALTER UNDO TABLESPACE undo_001 SET INACTIVE,等Innodb_history_list_length显著回落(比如从 20 万降到 2 万以下),才能TRUNCATE - MySQL 8.0.30+ 可用
OPTIMIZE TABLESPACE undo_001真实释放磁盘;低于该版本只能停库重建 undo 文件,风险高,务必提前评估
真正难的从来不是“怎么把数据搬过去”,而是“怎么让 InnoDB 认为这些旧版本已经没人需要了”。迁移脚本里少一个 COMMIT,可能就要花半天时间去救 purge 线程。


















