mysql默认repeatable read能防不可重复读,但实操中两次查询结果不一致的主因是连接未重连、事务未真正启动(begin后无sql不生成read view)、混用当前读(如for update),或误用非innodb引擎。

MySQL 默认的 REPEATABLE READ 隔离级别已能解决不可重复读问题,但很多人在实操中仍遇到“两次查询结果不一致”,根本原因不是隔离级别失效,而是连接复用、事务未正确启动或误用了当前读。
为什么设置了 REPEATABLE READ 还出现不可重复读?
常见错误现象是:明明执行了 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ,同一事务内两次 SELECT 却返回不同值。这通常不是 MySQL 实现有问题,而是以下几点被忽略:
- Navicat / DBeaver 等客户端连接未重连——修改的是会话级隔离级别,但已有连接不会自动刷新,必须断开重连才能生效
- 事务未真正启动:只执行
BEGIN但没执行任何 SQL,此时事务尚未建立 Read View;第一次SELECT才触发事务启动并生成快照 - 混用了当前读:比如在事务中执行了
SELECT ... FOR UPDATE或UPDATE,后续普通SELECT仍走快照读,但若中间有其他事务提交了修改,而你又在当前读之后做普通读,容易误判为“不一致” - 使用了非 InnoDB 引擎(如 MyISAM)——MVCC 不生效,
REPEATABLE READ无意义
如何验证当前事务是否真的在用快照读?
关键看事务启动时机和 Read View 是否固定。可借助 INFORMATION_SCHEMA.INNODB_TRX 和 SHOW ENGINE INNODB STATUS 观察事务状态,但更直接的方式是构造可复现场景:
- 开启事务 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 没走 MVCC 快照读,大概率是事务未真正启动,或隔离级别实际仍是
READ COMMITTED
哪些操作会破坏可重复读语义?
不是所有读都受 MVCC 保护。以下情况会让事务“看到新数据”,看似违反可重复读,实则是设计使然:
-
SELECT ... LOCK IN SHARE MODE和SELECT ... FOR UPDATE:属于当前读,读最新版本并加锁,不走快照 - 事务内执行了
INSERT/UPDATE/DELETE后再查同一行:InnoDB 会对本事务修改过的行做“最新可见性判断”,可能读到自己刚写的值,而非初始快照 - 跨事务查
information_schema或系统表:这些表不走 MVCC,始终读最新物理状态 - 使用
SELECT ... FROM t1 JOIN t2时,若 t2 被其他事务修改并提交,且该 JOIN 触发了索引合并或物化临时表,部分引擎行为可能绕过快照边界(少见,但 5.7 早期版本有案例)
真正容易被忽略的是:可重复读只保证“同一事务内对同一行的多次快照读结果一致”,它不承诺对“新插入行”或“其他事务刚提交的修改”完全屏蔽——幻读就是因此存在。如果你的业务逻辑依赖“绝对静止的数据视图”,得考虑显式加锁或升到 SERIALIZABLE,但代价是并发性能明显下降。











