mysql rr隔离级别下next-key lock是解决幻读的默认机制,它由record lock和gap lock组成,锁定左开右闭区间(如(2,4]),在范围查询或非唯一索引等值查询时生效,但主键等值查询仅加记录锁。

Next-Key Lock 是 RR 隔离级别下解决幻读的默认机制
MySQL 在 REPEATABLE READ(RR)隔离级别下,对 UPDATE、DELETE、SELECT ... FOR UPDATE 这类锁定读/写操作,默认启用 Next-Key Lock,不是为了“多锁点”,而是为堵住幻读漏洞。它本质是 Record Lock(锁记录本身) + Gap Lock(锁记录前的间隙),合起来锁住一个左开右闭区间:(prev_value, current_record]。
比如索引值有 2、4、8,执行 UPDATE ... WHERE c1 = 4,实际锁住的是 (2, 4] —— 不仅是 c1=4 这一行,还包括 c1=3 这个“空地”,防止其他事务插入 c1=3 或 c1=4 的新行。
这种设计在并发插入场景中很关键,但也是死锁和阻塞的常见源头。
非唯一索引或范围条件会强制触发 Next-Key Lock
主键或唯一索引上的等值查询(如 WHERE id = 100)通常只加 Record Lock;但只要满足以下任一条件,InnoDB 就会升级为 Next-Key Lock:
-
WHERE条件走的是非唯一辅助索引(例如status列有普通索引) - 使用范围操作符:
>、、<code>BETWEEN、LIKE 'abc%' - 出现
IS NULL、函数包裹字段(如WHERE YEAR(created_at) = 2024)、隐式类型转换
这些情况会让 InnoDB 认为“可能有多个匹配位置”,必须封锁间隙来防止幻读。即使你只更新一行,只要索引不唯一,它就可能锁住整个值对应的间隙段。
没索引 or 索引失效 → 实际效果接近全表锁
当 UPDATE 的 WHERE 条件无法命中有效索引时,Server 层只能全表扫描,引擎层则对每一条扫描到的聚簇索引记录都加 Next-Key Lock。这不是逻辑表锁,但效果一样:其他事务插不进、改不了、甚至读不了(若用 SELECT ... FOR UPDATE)。
典型诱因包括:
- 条件字段完全没建索引
- 对索引字段用了函数,如
WHERE UPPER(name) = 'ALICE' - 字符串字段用数字比较:
WHERE phone = 13800138000(phone 是 VARCHAR) - 联合索引未按最左前缀使用,如索引是
(a,b,c),却查WHERE b = 2 AND c = 3
此时 EXPLAIN 显示 type: ALL,rows 接近表总行数,基本可判定已退化为高危锁模式。
验证 Next-Key Lock 是否生效不能只看 SHOW ENGINE INNODB STATUS
SHOW ENGINE INNODB STATUS 里看到 lock_mode X locks rec but not gap 并不绝对可靠——它只反映当前锁状态快照,且容易漏掉间隙部分。更直接的方式是构造并发行为观察:
- 会话 A 执行:
UPDATE orders SET status = 'done' WHERE amount > 100(amount 有普通索引) - 会话 B 尝试:
INSERT INTO orders (amount, ...) VALUES (105, ...)→ 被阻塞 - 会话 B 改插
(50, ...)→ 可能成功,除非 50 恰好落在另一个已存在的间隙内
或者查 performance_schema.data_locks 表,关注 LOCK_DATA 字段:若出现类似 100, 105 这样的成对值,基本就是 Next-Key 区间在起作用。
真正难缠的不是锁本身,而是它不可见、不固定、依赖实时数据分布——同一语句在不同数据密度下锁的范围可能差几倍。线上排查时,别只盯着 SQL 写法,一定同步看索引结构和当前索引值分布。











