MySQL默认RR不是因更强一致性,而是为兼容早期STATEMENT格式binlog下的主从复制安全:RC会导致执行顺序与binlog记录错位,RR通过间隙锁强制串行化写入来规避。

MySQL默认RR不是因为一致性更强,而是为兼容旧版主从复制
MySQL把REPEATABLE READ设为默认,根本原因不是它“更安全”或“更适合业务”,而是早期(5.0及之前)binlog_format=STATEMENT下,READ COMMITTED会导致主从数据不一致。这种不一致不是偶发bug,而是可稳定复现的逻辑错误。
典型场景是:主库上一个事务先DELETE再INSERT同一条主键记录;由于READ COMMITTED下两个语句各自生成独立快照,且INSERT可能先提交,binlog就会按“先插后删”记录;从库重放时自然就只剩空表——主有数据、从没数据。
这个问题在REPEATABLE READ下被间隙锁(Gap Lock)间接挡住:DELETE会锁住索引间隙,后续INSERT被阻塞,强制执行顺序与日志顺序一致。
RC和RR在InnoDB中快照生成时机完全不同
这是开发中最容易误判的一点:很多人以为“RR事务开始就建快照”,其实不是。InnoDB里:
-
READ COMMITTED:每次SELECT都新建快照,看到其他事务最新已提交结果 -
REPEATABLE READ:**第一个SELECT(或SELECT ... FOR UPDATE等锁定读)才建立快照**,后续查询复用该快照
这意味着,在RR下执行SELECT前若已有其他事务提交了修改,你仍看不到——不是MVCC没生效,而是快照还没触发。而RC下只要SELECT执行时对方已提交,你就立刻看到新值。
现代MySQL用RC完全可行,但需主动切换并验证复制链路
MySQL 5.7+ 默认binlog_format=ROW,5.6起已支持MIXED,主从不一致风险基本消失。大厂如阿里、美团生产库普遍用READ COMMITTED,理由很实际:
- 降低长事务对undo log的占用(RR快照长期持有旧版本)
- 减少间隙锁范围,提升并发写入吞吐
- 语义更贴近开发者直觉:“我查的就是当前最新已提交数据”
但切换前必须确认:SHOW VARIABLES LIKE 'binlog_format'; 返回ROW或MIXED;且所有从库slave_type_conversions配置兼容(尤其涉及JSON、ENUM字段时)。
RR的幻读问题其实被高估了,但间隙锁副作用常被忽略
文档常说“RR不能完全解决幻读”,这没错——比如SELECT * FROM t WHERE id > 10后,另一事务INSERT INTO t VALUES (15),再次执行原查询确实会多出一行。但InnoDB用间隙锁+临键锁(Next-Key Lock)让这类INSERT被阻塞,实际业务中幻读往往表现为“插入失败”而非“读到新行”。
真正麻烦的是间隙锁的副作用:
- 范围查询(如
WHERE status = 0无索引)会锁全表间隙,导致无关更新被堵 -
UPDATE ... WHERE条件未命中任何行时,仍可能锁住相邻间隙,引发死锁 - 分页查询
LIMIT 10000,20在RR下锁住大量无用间隙,RC则完全不锁
这些不是理论风险,而是线上慢SQL和死锁日志里高频出现的具体模式。


















