REPEATABLE READ 并未真正消除幻读,而是通过间隙锁阻止新记录插入;READ COMMITTED 每次 SELECT 都新建快照,导致不可重复读;binlog_format 非 ROW 时切换隔离级别易致主从不一致;显式加锁和 CAS 更新比依赖隔离级别更可靠。

REPEATABLE READ 下的幻读问题其实没消失
很多人以为 REPEATABLE READ 能彻底避免幻读,但 MySQL InnoDB 的实现是通过间隙锁(Gap Lock)+ 行锁来“阻止”新记录插入,而不是真正消除幻读语义。这意味着:在同一个事务里两次执行 SELECT ... WHERE x > 10,第二次不会看到新插入的满足条件的行——但这只是锁机制压制了并发写入,并非快照隔离意义上的“无幻读”。
实际业务中容易踩的坑:
- 高并发插入场景下,
REPEATABLE READ可能导致大量间隙锁冲突,出现Lock wait timeout exceeded - 使用
SELECT ... FOR UPDATE时,InnoDB 会锁住范围,哪怕 WHERE 条件命中的是空结果集,也会加间隙锁 - ORM 自动生成的分页查询(如
WHERE id > ? LIMIT 20)在REPEATABLE READ下可能因间隙锁阻塞写入,拖慢吞吐
READ COMMITTED 不锁间隙,但每次 SELECT 都开新快照
READ COMMITTED 在 InnoDB 中意味着:每个 SELECT 语句都会生成一个新的一致性读视图(consistent read view),不复用事务内之前的快照。这直接导致同一事务中两次读取可能看到不同数据——不是脏读,而是“不可重复读”真实发生。
典型影响场景:
- 转账类逻辑若写成「先查余额 → 判断 → 更新」三步且未加锁,在
READ COMMITTED下第二步判断依据的余额可能已被其他事务改过 - 报表导出类任务如果跨多个
SELECT拼接数据,各表快照时间点不同,最终结果可能逻辑自洽但业务上失真(比如订单数和支付流水数对不上) - 触发器或存储过程中隐式调用的
SELECT,也各自开快照,行为更难预测
binlog 格式与隔离级别必须匹配
MySQL 启用 binlog_format = ROW 时,REPEATABLE READ 和 READ COMMITTED 对主从复制的影响差异不大;但若设为 MIXED 或 STATEMENT,问题就来了:
-
READ COMMITTED下的非确定性函数(如NOW()、UUID())在STATEMENT模式下可能导致主从不一致 - InnoDB 在
READ COMMITTED下不启用间隙锁,而某些 DML 语句(如DELETE ... LIMIT)在STATEMENTbinlog 中可能被重放为不同结果 - 线上切换隔离级别前,务必确认
binlog_format是ROW,否则复制延迟或数据错乱风险陡增
业务代码里的显式锁往往比隔离级别更可靠
依赖隔离级别“自动保护”很容易误判。比如认为 REPEATABLE READ 就能保证「查-改」原子性,实际上它只保证读结果不变,不保证后续更新不失败。
真正稳的做法是主动控制:
- 关键路径用
SELECT ... FOR UPDATE显式加锁,且确保 WHERE 条件能命中索引(否则升级为表锁) - 更新操作尽量走
UPDATE ... WHERE version = ?+ CAS 逻辑,比依赖事务隔离更直观可控 - 避免在事务里做 HTTP 调用、文件 IO 或长耗时计算——无论什么隔离级别,锁持有时间一长,所有并发优势都归零
最常被忽略的一点:应用层连接池配置的 defaultTransactionIsolation 可能被框架覆盖,上线前必须用 SELECT @@tx_isolation 实际验证,不能只信配置文件。










