锁等待超时在迁移中表现为“卡死”而非“慢”,因大事务未拆分导致行锁长期堆积,后续操作排队超50秒即报错;根本解法是用LIMIT+COMMIT分批处理并确保索引优化,而非调大innodb_lock_wait_timeout。

锁等待超时在迁移中不是“慢”,而是“卡死”——大事务一开,整张表的行锁堆在一起不释放,后续所有操作全排队等它,50 秒后一起崩。
为什么迁移时特别容易触发 Lock wait timeout exceeded
迁移通常走 INSERT ... SELECT 或逐条 UPDATE,但只要没控制事务粒度,InnoDB 就会把整个迁移过程包进一个事务:
- 事务开始后,每写一行就加一个行锁,不提交就不释放
- 若迁移涉及 10 万行,等于同时持有了 10 万个行锁(甚至更多,取决于索引结构)
- 此时业务请求想更新其中任意一行,就得排队等这个大事务结束
- 默认
innodb_lock_wait_timeout=50,等不到就报错,而不是重试或跳过
用 LIMIT + COMMIT 拆分 INSERT/UPDATE 语句
核心是让每个子事务只处理固定行数,并显式提交,避免锁长期堆积。不要依赖 ORM 自动提交或脚本里漏 COMMIT:
- 对
INSERT INTO t2 SELECT * FROM t1 WHERE ...,改写为分批循环:
SET @offset = 0; SET @batch_size = 5000; WHILE @offset
- 对逐条
UPDATE迁移(如补字段),用应用层控制批次,每次最多 100–500 行,执行完立刻COMMIT - 避免在循环里拼接超长 SQL,MySQL 有
max_allowed_packet限制,批量太大反而失败
注意 WHERE 条件是否走索引
拆分没用,如果 WHERE 不走索引,每次 LIMIT 查询仍要全表扫描,锁范围可能扩大成间隙锁甚至表级锁:
- 执行前先
EXPLAIN确认迁移语句的type是range或ref,不是ALL - 迁移表上缺失的索引(如按时间分批常用
created_at),必须提前建好 - 若原表无合适字段分片,可加临时自增列或用主键区间(
WHERE id BETWEEN ? AND ?)更安全
别碰 innodb_lock_wait_timeout 参数
调大它(比如设成 300)只会让业务请求卡得更久,不是解决问题,是掩盖问题:
- 全局修改
SET GLOBAL innodb_lock_wait_timeout = 300对已连接线程无效 - 单个会话设高了,用户端 HTTP 请求早超时了(Nginx 默认 60 秒),数据库还在等
- 真正该压测的是:拆分后每批执行耗时是否稳定在 1 秒内?这才是锁持有时间可控的标志
迁移不是比谁一次搬得多,是比谁搬得稳、不挡路。最容易被忽略的,是拆分逻辑里没校验最后一批是否真搬完了——LIMIT 遇到数据动态变化时可能漏行,得用主键 or 时间戳做幂等边界,而不是只靠计数。











