Next-Key Lock仅在唯一索引等值查询且记录存在时稳定退化为行锁;其余场景(记录不存在、非唯一索引、范围查询或额外扫描)均可能保留间隙锁成分甚至触发bug级加锁。

Next-Key Lock 只在唯一索引上的等值查询且记录存在时,才会稳定降级为行锁;其他任何情况——包括记录不存在、非唯一索引、范围查询、或哪怕只是多扫了一行——都可能保留间隙成分,甚至触发 bug 级别加锁。
唯一索引等值查询命中记录时必然退化为行锁
这是最明确的降级场景:主键或带 UNIQUE 约束的索引,执行 WHERE id = 16 这类查询,并且表中真实存在 id = 16 的行,InnoDB 就只对这一行加 X 锁(Record Lock),不会锁住 (8,16] 或 (16,20] 这样的区间。
- 必须是「唯一索引」:普通二级索引不适用此优化
- 必须是「等值查询」:
WHERE id IN (16)算,但WHERE id >= 16 AND id 是范围语义,不触发优化1 - 必须「记录真实存在」:查不到时会退化为间隙锁,不是行锁
- 事务隔离级别需为 RR 或串行化:RC 下根本不用 Next-Key Lock
非唯一索引等值查询不会降级,反而扩大加锁范围
比如对普通索引字段 c 执行 WHERE c = 5 FOR UPDATE,即使只有一行匹配,InnoDB 仍会按原则2向右扫描直到 c = 10 才停,导致加两个锁:(0,5] 和 (5,10];再按优化2把后者退化为间隙锁 (5,10)。最终锁住的是索引 c 上的记录 + 间隙,不是单行。
- 普通索引无法确认“是否唯一”,所以不敢退化
- 向右遍历是必须动作,只要没碰到第一个不满足条件的值就不会停
- 即使 SQL 看似只查一行,实际加锁范围可能是跨多条记录的区间
唯一索引范围查询会触发 bug,导致“不该锁的也锁了”
这是最容易踩坑的地方:WHERE id >= 8 AND id 理论上只该锁 <code>id = 8,但 MySQL 会继续访问下一个值(比如 id = 16),然后给 (8,16] 加 Next-Key Lock,再按优化2退化为间隙锁 (8,16) —— 结果是插入 id = 9 到 id = 15 全被阻塞。
- 这个行为是官方承认的 bug,不是文档承诺的“优化”
- 哪怕你用
id BETWEEN 8 AND 8,只要执行计划走范围扫描,就可能触发 - 用
EXPLAIN FORMAT=tree可观察是否真的只定位到单行;否则就得靠SELECT ... FOR UPDATE+SHOW ENGINE INNODB STATUS查 actual lock
为什么不能依赖“看起来是等值”就认为会降级?
因为优化是否生效,取决于执行路径而非 SQL 表面写法。例如:
-
WHERE id = ?使用预编译参数,如果传入 NULL 或类型不匹配,可能转为全表扫描,触发原则2的“访问所有对象都加锁” - 联合唯一索引如
(a,b),只写WHERE a = 1是范围查询,不触发优化1 - 索引前缀匹配(如
WHERE name LIKE 'abc%')不算等值,不降级 - 函数索引或虚拟列上的等值查询,在旧版本中可能绕过优化逻辑
真正决定是否降级的,是优化器最终选择的访问方式 + 索引结构 + 记录是否存在 —— 不是开发者写的 WHERE 子句长什么样。


















