rc性能优于rr的根本原因在于写操作锁范围更小:rc仅对命中行加记录锁且无间隙锁,rr则使用临键锁锁定记录及间隙;rc支持半一致性读可跳过被锁行,rr必须阻塞等待;rc每次select新建readview,rr复用事务级readview。

RC不加间隙锁,RR默认加临键锁
根本差异不在“读”,而在“写操作的锁范围”。UPDATE 或 DELETE 带范围条件时,RR 会自动升级为临键锁(gap lock + record lock),而 RC 只对实际命中的行加记录锁。
比如表 t 主键为 id,现有值 1, 3, 5, 7,执行 UPDATE t SET name='x' WHERE id > 3:
- RR 下:锁住
(3,5]、(5,7]、(7,+∞)三个区间 → 其他事务无法插入id=4、6、8,也无法更新id=5、7 - RC 下:仅对
id=5、7加记录锁 →id=4、6、8可自由插入,id=1、3也可被其他事务修改
锁范围越小,并发写冲突概率越低。这就是 Lock wait timeout exceeded 在 RR 下高频出现、切到 RC 后明显缓解的直接原因。
没索引时,RR会退化成全表锁扫描
当 WHERE 条件无法走索引,InnoDB 必须全表扫描。此时 RR 的行为是“边扫边锁”,把所有扫过的间隙都加上 gap lock;RC 则只在最终匹配的行上加记录锁,其余跳过。
常见错误现象:UPDATE t SET status=2 WHERE status=1(status 无索引):
- RR 下:全表扫描过程中,对每个间隙都加 gap lock → 实际效果接近表级阻塞,极易死锁
- RC 下:只锁住当前已提交且满足
status=1的那些行,扫描过程不提前加锁,也不锁间隙
这意味着即使你没显式写 SELECT FOR UPDATE,一个简单的无索引 UPDATE 在 RR 下也可能拖垮整个表的并发能力。
半一致性读让RC跳过被锁行,RR必须等
这是 RC 独有的优化机制,只在“更新被其他事务 X 锁住的行”时触发。它不是减少锁数量,而是避免无效等待。
假设事务 A 执行 UPDATE t SET x=1 WHERE y=20,扫描到某行时发现已被事务 B 加了 X 锁:
- RC 下:A 不立即阻塞,而是读该行最新已提交版本 → 若该版本不满足
y=20,直接跳过;若满足,才尝试加锁等待 - RR 下:不管该行是否满足条件、是否被锁,只要扫描到就加临键锁并阻塞等待
这个区别在 WHERE 条件选择率低(比如 y=20 实际只命中几行,但表里有大量 y≠20 的数据)时尤为明显——RC 能快速跳过大量无关行,RR 却在每行都卡住、加锁、再判断。
RC的ReadView是语句级的,RR是事务级的
虽然 MVCC 本身开销不大,但长期持有 ReadView 会带来两个隐性成本:
- 长事务下,
innodb_purge_thread无法清理该事务启动前生成的 undo log 版本 →ibdata1持续膨胀 - 高并发下多个长事务共用同一份 ReadView,undo 版本链更长,MVCC 查找路径变深,
SELECT延迟微升
RC 每次 SELECT 都新建 ReadView,不维护跨语句一致性,因此 purge 更及时、版本链更短、内存占用更低。这不是性能“优势”,而是更轻量的资源模型。
真正容易被忽略的是:间隙锁的禁用不是靠参数控制的,它是 RC 的协议级行为。哪怕你把 innodb_locks_unsafe_for_binlog 设为 ON(该参数在 MySQL 8.0+ 已移除),RC 下依然不加 gap lock —— 这个边界非常刚性,不能绕过,也不能妥协。











