唯一索引等值查询未命中时仍加锁,是因为InnoDB在RR隔离级别下为防幻读,必须对定位路径上访问的索引项加Next-Key Lock,并退化为间隙锁(如(5,10)),阻止其他事务插入该位置,即使无匹配记录。

唯一索引等值查询没命中数据,为什么还加锁
因为InnoDB的加锁行为不看“有没有找到记录”,只看“扫描路径上访问了哪些索引项”。哪怕WHERE id = 7查不到任何行,只要优化器决定走主键或唯一索引去定位,InnoDB就必须对定位过程中访问到的索引节点加锁——这是RR隔离级别防止幻读的底层机制。
典型表现是:事务A执行UPDATE users SET status = 1 WHERE id = 7 FOR UPDATE(id为主键,表中无id=7的记录),事务B紧接着执行INSERT INTO users (id, name) VALUES (7, 'a')会被阻塞。说明事务A确实持有了某个锁,且作用范围覆盖了“7该在的位置”。
唯一索引等值查询的加锁退化规则
Next-Key Lock是InnoDB默认加锁单位(左开右闭区间),但在唯一索引等值查询时会按实际结果退化:
- 记录存在 → 退化为
X 记录锁(只锁住该行,不锁间隙) - 记录不存在 → 退化为
X 间隙锁(锁住该值应插入位置前后的空隙,如(5, 10))
这个退化不是可选项,而是引擎强制行为。例如表中主键有1, 5, 10, 15,执行SELECT * FROM t WHERE id = 7 FOR UPDATE,InnoDB会定位到id = 10节点,然后对区间(5, 10)加间隙锁——它不锁10这行,但阻止别人往5和10之间插7或8。
怎么确认是否真加了锁,而不是“没反应”
不能只看EXPLAIN的rows=0就认为没锁。真实锁状态必须查系统表:
- 执行
SELECT * FROM performance_schema.data_locks,过滤当前事务ID,观察LOCK_MODE(如X,GAP)和LOCK_DATA(显示间隙边界) - 并发验证更直接:开启事务A执行
SELECT ... FOR UPDATE后不提交,事务B尝试INSERT或UPDATE同一索引范围内的值,看是否被Lock wait timeout exceeded或长期阻塞 -
SHOW ENGINE INNODB STATUS\G里TRANSACTIONS部分也能看到锁等待链,但信息较粗
RC隔离级别下会怎样
读已提交(RC)不使用Gap Lock,所以唯一索引等值查询即使没命中,也不会加任何锁。但注意:这不是“优化”,而是RR为解决幻读做的权衡。如果你的应用能接受RC,并且业务逻辑不依赖“多次查询结果集一致”,那切换隔离级别是最简单的规避方式。
真正容易被忽略的是:即使你写的SQL是UPDATE ... WHERE id = ?这种看似精准的操作,在RR下只要涉及唯一索引,就一定有锁——无论值存不存在。别被“没影响行数”骗了,间隙锁不体现在ROWS_AFFECTED里,但它实实在在卡着别的事务。


















