innodb在rr级别下对select加next-key lock导致锁表,全表扫描加剧锁定;ix锁阻塞ddl;分页用limit offset加重锁冲突,应改用索引切片;rc级别可减少锁但ix仍存在;无索引场景宜用应用层批量插入。

它不是“故意锁源表”,而是InnoDB在REPEATABLE READ隔离级别下,对SELECT扫描到的每一行加next-key lock(行锁+间隙锁)的自然结果;只要WHERE没走索引,就等效于锁全表。
RR隔离级别下SELECT部分默认是当前读
INSERT INTO SELECT中的SELECT不是快照读,而是当前读(current read),必须看到最新已提交数据,同时防止幻读。InnoDB为此对所有扫描行加next-key lock——既锁住该行,也锁住该行前后的间隙。这意味着其他事务无法更新、删除这些行,也不能在间隙中插入新行。
- 即使你只SELECT不INSERT,只要语句在RR事务中执行,这个加锁行为就存在
- EXPLAIN显示
type=ALL或rows远大于实际匹配数,说明走了全表扫描 → 锁覆盖范围极大 - 函数包裹索引字段(如
WHERE DATE(created_at) = '2026-08-01')会让索引失效,直接触发全扫
IX意向排他锁会阻塞DDL和X锁请求
源表上还会被加上IX(意向排他)锁,这不是为了读数据,而是告诉InnoDB:“我接下来要往目标表写,可能需要修改源表结构一致性”。这个锁本身不阻塞普通UPDATE,但会阻塞:ALTER TABLE、DROP INDEX、TRUNCATE等DDL,以及任何试图对源表加X(排他)锁的操作。
-
SHOW ENGINE INNODB STATUS里看到TABLE LOCK等待且lock_mode IX,基本可确认是此原因 - IX锁持有时间 = SELECT扫描耗时,所以扫描越慢,阻塞窗口越长
- MyISAM引擎下没有IX概念,但直接加表级读锁,效果更重
分批处理时LIMIT OFFSET反而加剧问题
用LIMIT 1000 OFFSET 10000分页迁移,MySQL每次都要从头扫描跳过前10000行,不仅性能线性下降,还导致每一批都重复加锁大量无关行,锁冲突概率翻倍。
- 正确做法是按主键或时间字段切片:
WHERE id BETWEEN 10001 AND 20000,确保每次扫描范围可控且无重叠 - 切片字段必须有索引,否则BETWEEN也退化为全表扫描
- 避免在WHERE中混用
OR、IN子查询等易让优化器放弃索引的写法
READ COMMITTED能减少锁但不能消除IX
把会话隔离级别临时设为READ COMMITTED,SELECT部分会变成快照读,不再加next-key lock——这是最直接降低源表阻塞的方式。但IX锁依然存在,因为INSERT动作本身仍需保证源表结构稳定。
- 执行前加:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; - 业务必须能容忍非重复读:同一事务内两次SELECT可能看到不同结果
- 目标表若含自增主键,
innodb_autoinc_lock_mode = 1(默认)仍会持AUTO-INC表级锁,大批量时建议调为2
真正难处理的是那些无法加索引的场景(比如JSON字段模糊匹配、文本LIKE前缀通配),这时别硬扛INSERT INTO SELECT,老实用应用层拉取+批量INSERT VALUES,虽然多了网络和内存开销,但锁边界清晰可控。











