为什么MySQL读已提交(RC)隔离级别能减少间隙锁死锁?

作者:袖梨 2026-07-14
RC默认不加间隙锁,RR默认加临键锁;RC通过半一致性读和早释放锁降低死锁概率,仅对命中行加锁,而RR对扫描范围全加锁并持至事务结束。

RC 默认不加间隙锁,RR 默认加临键锁

间隙锁(gap lock)是 RR 隔离级别为防止幻读而自动启用的机制,它不仅锁住匹配的行,还锁住索引记录之间的“间隙”。比如表中有 id 值 1、5、10,执行 UPDATE t SET name='x' WHERE id BETWEEN 3 AND 8,RR 会锁住 (1,5] 和 (5,10) 这两个间隙,导致其他事务无法插入 id=4id=6;而 RC 只对实际命中的 id=5record lock,间隙完全开放。

死锁常发生在多个事务按不同顺序尝试获取同一组间隙锁时。RC 下没有这种跨间隙的锁竞争,自然大幅降低死锁概率。

RC 的半一致性读让 UPDATE 不盲目加锁

当 UPDATE 扫描到已被其他事务加了 X lock 的行时,RC 会触发半一致性读(semi-consistent read):先读该行最新已提交版本,再判断是否满足 WHERE 条件;只对满足条件的行才尝试加锁。RR 则不管内容是否匹配、是否被锁,只要扫描到就直接加 next-key lock 并阻塞。

这意味着:

  • 无索引查询在 RC 下最多只锁住最终更新的几行
  • 同样语句在 RR 下可能锁住整个扫描范围(比如全表扫描 + 全表间隙锁),极易引发锁等待链和死锁

RC 锁释放更早,减少锁持有时间重叠

RC 中 UPDATEDELETE 语句执行完即释放所有未命中行的锁;RR 会把所有扫描过的行(包括不满足 WHERE 的)都加上 next-key lock,并一直持有到事务结束。

例如:UPDATE users SET status=2 WHERE created_at > '2026-01-01' 在 RR 下可能锁住几千行(哪怕只改 3 行),其他事务更新任意一行都会卡住;RC 只锁那 3 行,其余扫描行锁立刻释放——锁持有窗口窄,事务间冲突窗口自然小。

RC 下 INSERT 死锁仍可能发生,但诱因完全不同

RC 并非完全无间隙锁:在唯一键冲突检查、外键约束校验等场景下,仍会加 gap lock,此时也可能发生 INSERT 死锁。但这属于极少数显式约束场景,而非 RR 那样对每个范围查询都默认启用。

所以真正要警惕的不是“RC 有没有间隙锁”,而是:

  • 是否在 RC 下误以为“绝对安全”,忽略了唯一索引冲突时的隐式间隙锁
  • 是否在应用层没处理好不可重复读,反而靠加锁硬扛,人为引入新死锁点

间隙锁死锁的减少,本质是把锁的“默认行为”从 RR 的保守覆盖,转向 RC 的按需最小化——但这个“按需”,得靠你写对 SQL、建好索引、理清业务逻辑才能兑现。

相关文章

精彩推荐