Next-Key Lock的加锁单位是左开右闭区间,本质是Record Lock与Gap Lock的自动组合;在RR隔离级别下,范围查询或非唯一索引查询默认启用,主键等值查询则退化为纯Record Lock。

Next-Key Lock 的加锁单位是左开右闭区间
Next-Key Lock 不是一种独立锁类型,而是 Record Lock(锁记录)和 Gap Lock(锁间隙)的自动组合,锁定形式为 (prev_value, current_record]。比如索引值有 10、20、30,执行 SELECT * FROM t WHERE id BETWEEN 15 AND 25 FOR UPDATE,InnoDB 实际锁定的是 (10, 20] 和 (20, 30] 两个区间——既锁住 id = 20 这行,也锁住 id = 15、18、22 等可能插入的位置。
这个“左开右闭”特性直接决定哪些插入会被阻塞:在 (10, 20] 区间内,INSERT INTO t VALUES (15) 会被拦住,但 INSERT INTO t VALUES (10) 不会(因为 10 不在开区间内),INSERT INTO t VALUES (20) 也不会(因为 20 是右端点,已被 Record Lock 覆盖)。
唯一索引等值查询会退化为 Record Lock
当使用主键或唯一索引做 WHERE id = 100 类查询时,只要该值存在,InnoDB 就只加 Record Lock,不锁任何间隙。这是明确的优化行为,不是 bug 或配置问题。
-
SELECT * FROM users WHERE id = 5 FOR UPDATE→ 只锁id = 5这一行 -
UPDATE orders SET status = 'done' WHERE order_no = 'ORD-001'(order_no是唯一索引)→ 同样只锁匹配行,其他事务仍可向id = 4和id = 6之间插入新订单 - 但若
order_no = 'ORD-999'不存在,就会退化为Gap Lock,锁定上一个存在的值到下一个存在的值之间的间隙,比如(ORD-001, ORD-002)
非唯一索引或范围查询默认启用 Next-Key Lock
只要 WHERE 条件没走唯一索引,或者用了范围操作符,InnoDB 就按默认规则上 Next-Key Lock。哪怕你只更新一行,只要索引不唯一,它就可能锁住整个值段对应的间隙。
常见触发场景包括:
-
WHERE status = 'pending'(status是普通二级索引)→ 锁(prev_status, 'pending']区间 -
WHERE created_at > '2024-01-01'→ 锁从第一个大于该时间的记录开始,一直到+∞ -
WHERE name LIKE 'abc%'→ 前缀匹配算范围查询,同样触发 Next-Key Lock -
WHERE YEAR(created_at) = 2024→ 函数包裹字段导致索引失效,InnoDB 可能全表扫描并给每行加锁
没索引或索引失效时,效果接近全表锁:Server 层逐行判断,引擎层对每条聚簇索引记录都加 Next-Key Lock。
Gap Lock 和 Insert Intention Lock 的冲突常引发死锁
间隙本身支持并发读写(Gap Lock 之间完全兼容),但一旦有事务尝试往同一间隙插入数据,就会申请 Insert Intention Lock —— 它是一种特殊的间隙锁,与普通 Gap Lock 兼容,却与 Next-Key Lock 冲突。
典型死锁链:
- 事务 A 执行
UPDATE t SET x=1 WHERE a = 5(a 是非唯一索引),锁住(3, 5] - 事务 B 同时执行
INSERT INTO t (a) VALUES (4),需申请Insert Intention Lock在(3, 5)间隙 - 事务 A 随后又想插一条
a = 4,被事务 B 的插入意向锁挡住;事务 B 的插入又被事务 A 的 Next-Key Lock 挡住 → 循环等待
这种死锁不会出现在主键等值更新场景里,但凡涉及非唯一索引或范围条件,就得警惕间隙锁的隐式扩散效应。


















