innodb在update未命中行时仍加锁,是因为其锁机制基于索引扫描范围而非实际匹配行,会施加间隙锁或临键锁以防止幻读,确保rr隔离级别下的一致性。

UPDATE没找到行,为什么还加锁?
因为InnoDB的锁不是“只锁命中的行”,而是“锁住WHERE条件扫描到的所有索引位置”——哪怕最终没匹配到任何记录,只要走索引扫描了范围,就会加间隙锁(Gap Lock)或临键锁(Next-Key Lock)。
典型场景:执行 UPDATE t SET x=1 WHERE id > 100 AND id ,但表里根本不存在 id 在 (100,200) 区间的记录。InnoDB 仍会锁定这个间隙,防止其他事务插入 id=150 的新行,这是可重复读(RR)隔离级别下防幻读的强制行为。
- 如果
id是主键或唯一索引,且条件是等值查询(如WHERE id = 999),而该值不存在 → 加Gap Lock,锁住前一条和后一条记录之间的空隙 - 如果
id是普通索引,且条件是范围(如WHERE id BETWEEN 100 AND 200)→ 加Next-Key Lock,锁住匹配区间及所有间隙 - 如果
WHERE没走索引(全表扫描)→ 实际效果等同于锁全表,不是“没锁”,而是“锁了所有扫描行 + 意向锁”,并发彻底阻塞
如何验证没命中也加锁?
用 SHOW ENGINE INNODB STATUS\G 查看锁信息,重点关注 lock_mode X locks gap before rec 或 lock_mode X locks rec but not gap 这类提示。它不会出现在 INFORMATION_SCHEMA.INNODB_TRX 中,因为间隙锁本身不可见。
实操建议:
- 在事务A中执行
UPDATE t SET v=1 WHERE k > 500(k有索引但无数据满足)并保持未提交 - 在事务B中尝试
INSERT INTO t (k,v) VALUES (450, 'x')→ 成功;但INSERT INTO t (k,v) VALUES (550, 'x')→ 被阻塞 - 说明间隙 (500, next_existing_value) 已被锁住,即使没命中的行也影响了插入
为什么不能跳过锁?
这不是设计缺陷,而是RR隔离级别下保证一致性必须付出的代价。InnoDB 必须阻止“幻读”:否则事务A查不到 id>100 的记录,事务B插入一条,事务A再查就出现了新行,违反可重复读语义。
关键点:
-
READ COMMITTED级别下不加间隙锁,只锁实际匹配的行 → 没命中就不锁,但可能幻读 - 业务若只做等值更新、且能容忍幻读,可考虑临时切隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED - 加索引不能解决“没命中也锁”的问题,但能避免全表扫描导致的伪表锁——这是更危险的场景
容易忽略的陷阱
很多人以为“UPDATE影响0行 = 没开销”,其实不然。锁等待、锁升级、死锁风险依然存在,尤其当多个事务同时执行类似“插入前检查是否存在”的逻辑时(如 UPDATE ... WHERE key = ? 试图乐观更新),极易因间隙锁互相阻塞。
真正要警惕的不是“没数据”,而是“WHERE条件落在索引范围内但无数据”——这种操作在高并发写入路径中,比你想象中更容易成为瓶颈。











