不可重复读发生在READ COMMITTED隔离级别下,当一个事务内两次SELECT同一条记录时因其他事务提交UPDATE导致结果不一致;其根源是该级别采用语句级快照,每次SELECT都读取最新已提交版本。

不可重复读到底在什么场景下发生
当一个事务内两次 SELECT 同一条记录,结果不一致(比如余额从 100 变成 50),且中间被另一个事务 UPDATE 并提交了——这就是不可重复读。它只在 READ COMMITTED 隔离级别下会出现,REPEATABLE READ(RR)默认会挡住它。
READ COMMITTED 为什么挡不住不可重复读
因为它的快照是“语句级”的:每次执行 SELECT 都重新取一次最新已提交版本。前一次查完,别人改完提交,下一次查就看到新值。
-
READ COMMITTED下,SELECT不加锁(仅用 MVCC 快照),也不阻塞并发UPDATE - Oracle、SQL Server 默认就是这个级别,MySQL 的
READ COMMITTED行为也类似 - 适合读多写少、对“两次读一致”没强要求的场景,比如日志汇总、监控看板
REPEATABLE READ 怎么做到两次读一样
MySQL InnoDB 的 REPEATABLE READ 在事务第一次 SELECT 时就建立一致性视图(consistent read view),后续所有 SELECT 都复用这个快照,不管其他事务是否已提交。
- 注意:这不等于“全程加锁”,只是读不阻塞写,写也不被读阻塞(靠 MVCC 实现)
- 但
UPDATE/DELETE会基于最新行版本加锁,所以可能触发Lock wait timeout exceeded - 幻读依然存在(
INSERT新行能被后续SELECT看到),需配合SELECT ... FOR UPDATE或间隙锁抑制
切换隔离级别时最容易踩的坑
直接改全局变量 SET GLOBAL tx_isolation = 'REPEATABLE-READ' 不影响已有连接,只对新连接生效;而会话级设置 SET SESSION tx_isolation = 'READ-COMMITTED' 又容易被 ORM 框架覆盖(比如 Spring 的 @Transactional(isolation = ...) 会重置)。
- 应用层显式指定隔离级别优先级高于数据库配置,务必确认 ORM 是否透传
-
READ COMMITTED下做“先查后更”,大概率遇到Lost Update,得靠SELECT ... FOR UPDATE或应用层 CAS - 高并发下
REPEATABLE READ的快照可能占用大量 undo log,长时间事务易导致 purge 延迟和空间膨胀
STATEMENT(RR 才能保证主从一致)。这些细节比隔离级别名字本身更关键。










