临键锁锁定左开右闭区间(gap_start, record],如(age=13)时锁(10,13],既防修改又防插入;唯一索引等值查询且值存在时退化为纯记录锁。

临键锁到底锁了哪一段数据
临键锁不是“锁住某一行”,而是锁住一个左开右闭的区间:(gap_start, record]。比如索引值为 10、13、20 的三行,查询 WHERE age = 13 FOR UPDATE 在 RR 隔离级别下,实际加的是 (10, 13] 这个临键锁——它既拦住了对 age=13 这条记录的修改(记录锁),也拦住了在 10 和 13 之间插入 age=11 或 12 的新行(间隙锁)。
- 只对**已存在索引值**做等值查询时,InnoDB 可能优化掉间隙部分,退化为纯记录锁(
REC_NOT_GAP),但前提是该索引是**唯一索引且值存在** - 范围查询(如
WHERE age > 10 AND age )必然触发多个临键锁,覆盖整个扫描范围 - 若查询条件未命中任何索引(例如用非索引字段
name查询),InnoDB 会升级为表锁,临键锁机制完全失效
为什么RR隔离级别默认用临键锁
因为它是 InnoDB 在可重复读(RR)下防止幻读的底层手段。幻读不是“读到别人刚提交的新行”,而是“同一查询条件,两次执行返回行数不同”——这往往由并发 INSERT 引起。临键锁通过封锁“记录+前面间隙”,让其他事务无法在你查过的范围内插入新匹配行。
- RC(读已提交)隔离级别下,InnoDB 不用临键锁,只用记录锁,所以幻读可能发生
- 即使开了 MVCC,快照读(普通
SELECT)不加锁,但当前读(SELECT ... FOR UPDATE、UPDATE、DELETE)仍依赖临键锁保证写一致性 - 不要误以为“RR 就绝对没幻读”:如果业务用的是快照读 + 应用层判断再写入(所谓“先查后插”逻辑),依然可能因 MVCC 快照滞后导致幻读
怎么验证当前语句加了什么锁
别猜,直接查 performance_schema.data_locks。这是最可靠的方式,尤其当你怀疑锁范围过大或阻塞异常时。
- 执行
SELECT * FROM performance_schema.data_locks WHERE OBJECT_SCHEMA = 'your_db' AND OBJECT_NAME = 'sys_user'; - 看
LOCK_MODE字段:S或X表示共享/排他,LOCK_DATA显示具体锁定的索引值或间隙(如13或10, 13) - 注意
LOCK_TYPE是RECORD还是GAP或NEXT-KEY;LOCK_DATA为supremum pseudo-record表示锁住了最大值之后的间隙 - 务必在事务未提交时查,否则锁已释放,查不到结果
容易被忽略的三个实战陷阱
临键锁的“组合性”和“隐式性”让它在真实场景中容易误伤:
- 普通二级索引(非唯一)上的等值查询,哪怕只查一条,也会锁住整个临键区间,不只是那条记录本身
-
INSERT ... ON DUPLICATE KEY UPDATE在遇到唯一键冲突时,会对冲突行加临键锁,而不是简单跳过——这常导致本不该阻塞的插入被卡住 - 自增主键插入时,InnoDB 会在自增值末尾加一个间隙锁(
gap lock on supremum),防止其他事务插入“更大 id”,这个锁在事务提交后才释放,高并发插入可能因此排队
真正难的从来不是记住定义,而是看到 INSERT 被阻塞时,能不能快速反应出:是不是前一个事务在隔壁间隙里留了一把没释放的临键锁。










