MySQL默认REPEATABLE READ能防止不可重复读,但两次查询结果不一致通常因事务未真正启动、连接复用、误用当前读或非InnoDB引擎导致快照读未生效,并非隔离级别缺陷。

MySQL 默认的 REPEATABLE READ 隔离级别本身就能防止不可重复读,问题通常出在事务没真正启动、连接未重连或误用了当前读,而不是隔离级别设置错了。
为什么设了 REPEATABLE READ 还出现两次查询结果不一致?
这不是 MySQL 实现有缺陷,而是常见实操疏漏导致 MVCC 快照没生效:
- 客户端(如 Navicat、DBeaver)复用旧连接:执行
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ后未断开重连,会话仍沿用旧隔离级别 - 只执行
BEGIN但没跟任何 SQL:事务尚未真正启动,SELECT第一次执行时才生成Read View,此前修改已提交就会被“看到” - 混用
SELECT ... FOR UPDATE或UPDATE:这些是当前读,读最新版本并加锁;之后再执行普通SELECT虽走快照,但人容易误以为“刚读到新值,怎么又变回去了?” - 表引擎不是 InnoDB:MyISAM 等引擎不支持 MVCC,
REPEATABLE READ形同虚设
如何确认事务是否真的在用快照读?
关键看 Read View 是否固定、是否被复用。最直接验证方式是构造可复现步骤:
连接 A:BEGIN → SELECT * FROM bank_account WHERE id = 1(记下 balance)
新开连接 B:UPDATE bank_account SET balance = balance + 100 WHERE id = 1 → COMMIT
回到连接 A:SELECT * FROM bank_account WHERE id = 1(应与第一次完全一致)
若第二次查询结果变了,说明:
- 连接 A 的事务实际隔离级别仍是
READ COMMITTED(可用SELECT @@transaction_isolation确认) - 或连接 A 根本没真正启动事务(比如
BEGIN后直接切走,第一次SELECT才触发事务初始化) - 或表结构用的是
ENGINE=MyISAM(查SHOW CREATE TABLE bank_account确认)
哪些操作会绕过可重复读语义?
不是所有读都受 MVCC 保护。以下行为会让事务“意外看到新数据”,看似违反可重复读,实则是设计如此:
-
SELECT ... LOCK IN SHARE MODE和SELECT ... FOR UPDATE:属于当前读,跳过快照,直接读最新已提交版本并加锁 - 事务内先
UPDATE某行,再SELECT同一行:InnoDB 对本事务修改过的行做“最新可见性判断”,返回自己刚写的值,而非初始快照 - 查
information_schema或系统表(如INFORMATION_SCHEMA.INNODB_TRX):这些表不走 MVCC,始终读物理最新状态 - 执行
START TRANSACTION WITH CONSISTENT SNAPSHOT:它强制立即生成Read View,但若之前已有活跃事务未提交,该快照可能包含部分未提交变更(取决于具体场景)
真正容易被忽略的是事务启动时机和连接生命周期——BEGIN 不等于事务启动,SET SESSION 不等于连接刷新。MVCC 的一致性保障,依赖于“一个事务对应一个固定 Read View”,而这个前提必须由开发者主动确保。


















