mysql如何利用临键锁(Next-Key)_mysql范围加锁详解

5次阅读

next-key lock 是 record lock 与 gap lock 的组合,锁定左开右闭区间 (prev_value, current_record],核心用于解决幻读;其范围依赖索引结构和实时数据分布,非固定“范围锁”。

mysql如何利用临键锁(Next-Key)_mysql范围加锁详解

Next-Key Lock 是什么,为什么不是“范围锁”那么简单

mysqlNext-Key Lock 不是单独一种锁,而是 Record Lock(行锁) + Gap Lock(间隙锁)的组合。它锁定的是「索引记录本身」和「该记录前一个索引值到当前记录之间的间隙」——换句话说,它锁住的是一个左开右闭区间:(prev_value, current_record]。这个设计核心目标是解决幻读,但副作用是:哪怕你只查 WHERE id > 10 AND id ,InnoDB 也可能锁住 <code>id = 20 这条记录(如果它存在),甚至锁住 id = 20 后面的空隙。

哪些查询会触发 Next-Key Lock

关键看是否满足两个条件:使用**可重复读(RR)隔离级别** + **走索引(最好是唯一索引以外的索引)**。主键或唯一索引等值查询(如 WHERE id = 15)只会加 Record Lock;而范围查询、LIKE 前缀匹配、非唯一索引上的等值查询,都会激活 Next-Key Lock

  • select ... for UPDATEUPDATE/delete 走非唯一索引时,锁整个扫描范围的 Next-Key 区间
  • WHERE status = 'pending'status 有非唯一索引)→ 锁所有匹配值及其前间隙,可能比你想象的宽得多
  • WHERE created_at > '2024-01-01' → 锁住第一个大于该时间的记录,以及它前面的间隙,还可能延伸到索引末尾
  • 如果查询不走索引(全表扫描),InnoDB 会退化为锁所有聚簇索引记录 + 所有间隙 → 实际上接近表级锁效果

如何验证 Next-Key Lock 正在生效

不能只看 SHOW ENGINE INNODB STATUS 里的 lock_mode X locks rec but not gap 这类描述——它容易误导。真正可靠的方式是构造并发操作并观察阻塞行为:

  • 会话 A 执行:SELECT * FROM orders WHERE amount > 100 FOR UPDATEamount 有普通索引)
  • 会话 B 尝试插入 amount = 105 → 被阻塞(因为 105 落在某个 Next-Key 区间内)
  • 会话 B 尝试插入 amount = 50 → 可能成功(除非它刚好掉进另一个已存在的间隙中)
  • SELECT * FROM performance_schema.data_locks 查看 LOCK_DATA 字段,若显示类似 100, 105105, 110 这种成对值,基本就是 Next-Key 区间

常见踩坑点:明明没改数据,为什么还锁表?

最典型的是在非唯一索引上做 UPDATE ... WHERE non_unique_col = ?。即使只命中一行,InnoDB 仍会锁住该值对应的所有可能位置——包括它前面的间隙,防止其他事务插入“相同值”的新行(避免幻读)。这导致看似单行更新,却卡住一批后续插入。

  • 误以为 SELECT ... LOCK IN SHARE MODE 安全 → 它同样用 Next-Key Lock,只是锁类型是 S 而非 X
  • 忽略索引选择性:低选择性字段(如 gender)建了索引,WHERE gender = 'M' 可能锁住整个索引段
  • 在 RR 级别下用 INSERT ... SELECT,源表扫描过程也会加 Next-Key Lock,影响并发度
  • 唯一索引失效(比如隐式类型转换)会导致降级为普通索引 → Next-Key Lock 意外激活

Next-Key Lock 的边界依赖于索引结构和现有数据分布,没有预设“范围”,每次执行都得现场计算。这意味着同样的 SQL,在不同数据状态下锁的区间可能完全不同——这点最容易被忽略。

text=ZqhQzanResources