源表被锁的主因是rr隔离级别下的next-key lock,而非语句本身;当where条件无索引时触发全表扫描,innodb对所有扫描行及间隙加next-key锁,导致其他事务更新、删除或插入均被阻塞。

为什么源表会被锁:RR隔离级别 + next-key lock 是主因
不是语句本身要锁源表,而是 InnoDB 在 REPEATABLE READ(默认)下,为保证可重复读和防止幻读,会对 SELECT 扫描到的每一行加 next-key lock(行锁 + 间隙锁)。只要 WHERE 条件没走索引,就会全表扫描 → 锁住所有行和间隙 → 其他事务更新/删除这些行、或在间隙插入新数据,全部被阻塞。
哪些情况会让锁“等效于锁表”
以下场景极易触发大面积阻塞:
-
WHERE条件无索引,或索引未被命中(如函数包裹:WHERE DATE(created_at) = '2023-01-01') - 源表数据量大(比如超 10 万行),且语句执行时间长,锁持有时间随之拉长
- 目标表有自增主键,且
innodb_autoinc_lock_mode = 1(默认),大批量插入会持有一个较重的 AUTO-INC 表级锁 - 使用 MyISAM 引擎——它不支持行锁,
INSERT INTO SELECT直接对源表和目标表都加表级锁
分批处理时为什么不能只用 LIMIT OFFSET
LIMIT + OFFSET 在高并发下极不稳定:
- 中间有新数据插入时,OFFSET 会跳过或重复某些行(因为
SELECT没有ORDER BY,顺序不保证) - 每次 OFFSET 越大,MySQL 越要跳过前面所有行,性能线性下降
- 更可靠的做法是按主键或时间字段切片:
WHERE id BETWEEN 10000 AND 20000或WHERE created_at > '2023-01-01' AND created_at
读写分离能解决这个问题吗
不能。原因很直接:
-
INSERT INTO SELECT是写操作,必须在主库执行 → 源表和目标表的锁全部落在主库 - 从库即使配置了复制,也只是事后同步;它不会分担主库的锁压力
- 如果误把该语句发到从库(比如 binlog 过滤规则错配),反而可能因从库负载高或延迟加剧问题
真正可行的绕过方式,是把 SELECT 拆出来,在应用层取数后再拼 INSERT VALUES 批量写入——但这已不是原语句范畴,且引入网络传输、内存占用、事务一致性等新复杂度。











