不能用单条INSERT…SELECT迁移千万级大表,因其会锁源/目标表、引爆事务日志、引发超时与max_allowed_packet错误;InnoDB全表扫描易升级为间隙锁甚至表级等待,导致业务写入卡顿;必须用主键范围切片分批,避免LIMIT OFFSET,并确保主键单调递增无空缺。

为什么不能用单条 INSERT … SELECT 迁移大表
直接 INSERT INTO target SELECT * FROM source 在千万级以上数据量时会触发严重问题:锁住源表或目标表、事务日志暴涨、超时中断、max_allowed_packet 报错。InnoDB 的行锁在全表扫描+插入过程中容易升级为间隙锁甚至表级等待,业务写入会明显卡顿甚至阻塞。
分批迁移必须依赖主键/有序字段做游标切片
没有连续自增主键(如 id)的表,分批逻辑会失去锚点,极易漏数据或重复。实际操作中要确认:
- 源表必须有可排序、无空缺、单调递增的整型主键(或带索引的唯一时间字段)
- 避免用
LIMIT offset, size分页——offset 越大越慢,且并发下可能跳过新插入行 - 每次迁移都基于上一批的
MAX(id)值过滤,例如WHERE id > @last_id AND id - 批次大小建议 1000–10000 行,太小增加循环开销,太大仍可能锁表
存储过程里必须显式控制事务与提交节奏
默认自动提交模式下,每条 INSERT 都是一次事务,性能极差;全包在一个事务里又风险太高。正确做法是:
- 用
START TRANSACTION包裹每一批次的INSERT和UPDATE记录逻辑 - 每批结束后立即
COMMIT,释放锁和 undo 日志空间 - 在循环体外建一张迁移状态表(如
tmp_migrate_log),每次成功后REPLACE INTO更新last_migrated_id - 务必加
SELECT SLEEP(0.01)或类似微延迟,缓解主从复制压力和 binlog 写入抖动
容易被忽略的三个硬性前提
再完善的存储过程,缺了这三点也会失败:
-
binlog_format必须为ROW,否则增量同步工具(如 Canal)无法捕获变更 - 目标表结构需提前建好,且禁用非必要索引(尤其是二级索引),等全量完成后统一
ALTER TABLE ... ENABLE KEYS - 源库
innodb_flush_log_at_trx_commit可临时设为2,降低刷盘频率(仅限迁移窗口期)
真正难的不是写循环,而是让每一批都可中断、可重试、不丢不重——所有状态必须落盘,所有边界条件(如最后一批不足 batch_size、主键跳变、NULL 值)都要显式判断。











