mysql在rr级别下,where id between 10 and 20加临键锁,实际锁定(9,10]、(10,15]、(15,20]和(20,25)区间,因扫描访问索引节点而定,非仅由where条件决定。

MySQL 在 RR 隔离级别下,WHERE id BETWEEN 10 AND 20 这类范围查询会锁住远超你肉眼所见的行——它实际加的是 临键锁(next-key lock),覆盖记录本身 + 后续间隙,比如锁住 (9, 25) 区间,导致插入 id = 21 的事务被阻塞。这不是 bug,是 InnoDB 为防止幻读的主动行为。
Range 查询到底锁了哪些区间?看执行路径和索引结构
加锁范围不取决于 WHERE 写了什么,而取决于「InnoDB 扫描过程中访问到的索引节点」。主键或唯一索引上范围扫描时,每走一个 next-key 区间都会加锁;非唯一索引则更激进,可能向右连续扩展多个区间。
- 假设主键有值
9, 10, 15, 20, 25,执行UPDATE t SET x=1 WHERE id BETWEEN 10 AND 20 - InnoDB 从
id = 10开始扫描,命中后加(9, 10](优化1退化为记录锁) - 继续向右找,碰到
15→ 加(10, 15],再碰到20→ 加(15, 20] - 但扫描不会停在
20,它要确认「下一个值是否还 ≤ 20」,于是读到25,发现超限 → 此时(20, 25)被加为间隙锁(优化2触发) - 最终锁区间是:
(9, 10]、(10, 15]、(15, 20]、(20, 25)—— 注意最后一个只是 gap,不包含 25
为什么 id = 21 插入会被卡住?
因为 21 ∈ (20, 25),而这个间隙已被前一个事务用 X 型间隙锁占据。此时另一个事务执行 INSERT INTO t (id) VALUES (21),需申请「插入意向锁(insert intention lock)」,但该锁与间隙锁互斥 —— 这就是阻塞根源。
-
lock_mode X locks gap before rec出现在SHOW ENGINE INNODB STATUS中,说明当前事务正卡在等待某个间隙释放 -
insert intention出现在 WAITING 行,且 LOCK_DATA 显示21,基本可断定是掉进了(20, 25)这个坑 - 间隙锁之间彼此兼容,但唯独和插入意向锁不兼容 —— 这个设计才是死锁温床,不是锁太多,而是锁类型冲突
怎么缩小 Range 查询的锁范围?
不能靠“写得更精确”,而要控制「扫描路径」和「索引选择」。RR 下没有“只锁命中的行”这种事,除非你能让优化规则生效。
- 确保条件列走唯一索引(主键或 UNIQUE):只有这时优化1才起作用,范围内的记录锁可退化为纯记录锁,减少间隙部分
- 避免在非唯一索引上做范围查询:例如
WHERE c BETWEEN 5 AND 10(c是普通 KEY),InnoDB 可能一路扫到c = 15才停,连带把(10, 15)也锁了 - 用等值代替范围:如果业务允许,拆成
id IN (10,11,12,...,20),前提是这些值全部存在且走主键 —— 这样每个都是独立记录锁 - 考虑降级隔离级别:设为
READ COMMITTED可彻底关闭间隙锁,但需同步设置binlog_format = ROW防止主从不一致
真正难处理的不是“怎么查锁”,而是“怎么让 InnoDB 别在你不关心的间隙上加锁”。它不听你 WHERE 怎么写,只看你扫描时经过了哪些索引位置 —— 看执行计划比看 SQL 更管用。











