MySQL在RC级别下死锁比RR少,因RC不加间隙锁、仅对命中行加记录锁,锁范围更小;支持半一致性读跳过被锁且不满足条件的行;每次SELECT新建ReadView降低MVCC开销。

RC不加间隙锁,锁范围天然更小
死锁高频发生在多个事务对同一组间隙反复争抢时。RR级别下,UPDATE WHERE age > 25这类范围条件会自动加Next-Key Lock(记录锁 + 间隙锁),锁住整个区间;而RC只对实际命中的行加Record Lock,间隙不被锁定。
常见死锁链:事务A锁了(10, 20)间隙,事务B锁了(20, 30),然后都试图插入25 → 直接卡死。RC下这个间隙没被锁,Insert Intention Lock之间天然兼容,基本不会触发循环等待。
注意:唯一索引冲突、外键约束等极少数场景,RC仍会加Gap Lock做校验,这点常被忽略。
RC支持半一致性读,跳过被锁但不满足条件的行
当事务A执行UPDATE t SET x=1 WHERE y=20,扫描到某行已被事务B加了X锁时:
- RC下:先读该行最新已提交版本;若
y ≠ 20,直接跳过,不加锁也不等待 - RR下:不管是否满足条件、是否被锁,只要扫到就加
Next-Key Lock并阻塞等待
这意味着在无索引字段上做更新时,RC最终只锁住真正匹配的行;RR却可能边扫边锁所有间隙,甚至接近全表阻塞。
RC每次SELECT新建ReadView,MVCC开销更低
RR级别下,事务第一次SELECT就生成一个全局ReadView并复用到结束,需长期维护undo log版本链;RC每次查询都新建ReadView,不保留事务级快照。
写多读少场景下,RC的undo log清理压力更小,版本链更短,间接降低因版本回溯引发的锁等待或超时概率。
RC下INSERT死锁的真实诱因容易被误判
很多人以为RC完全不会INSERT死锁,其实不然。关键陷阱在唯一键冲突场景——RC和RR在此类行为上完全一致:
- 两个事务并发执行
INSERT INTO users (id, email) VALUES (100,'a@b.com'),而email是唯一索引且已有同值记录 → 两者都会对已存在记录加S型Next-Key Lock,再尝试插入新行,形成X与S互斥 -
INSERT ... ON DUPLICATE KEY UPDATE在冲突时加X型Next-Key Lock,RC照常死锁 -
REPLACE INTO更激进:会在冲突记录及其下一条记录上都加Next-Key Lock
真正复杂的地方不在“RC有没有死锁”,而在于:哪些操作看似安全,实则仍在走唯一索引冲突路径;哪些“没加索引”的WHERE条件,在RC下看似只锁行,却因全表扫描+大量Record Lock埋下隐性冲突点。


















