RR隔离级别下,ReadView在事务第一次快照读时生成并全程复用,通过固定m_ids、min_trx_id、max_trx_id和creator_trx_id判断版本可见性,确保同一事务内多次读取结果一致;而RC级别每次快照读都新建ReadView,故不保证可重复读。

RR隔离级别下,Read View复用是关键
可重复读(RR)能解决不可重复读,根本不是靠“拍快照”这种模糊说法,而是事务第一次快照读时生成一个 ReadView,之后整个事务都复用它。这个 ReadView 里存着当时活跃事务的 m_ids、min_trx_id、max_trx_id 和自己的 creator_trx_id —— 它决定了哪些版本可见,且不会变。
常见误解是“事务一启动就建 ReadView”,其实不是:BEGIN 不触发,真正建视图的时机是事务内**第一条快照读语句**(比如第一个 SELECT)执行时。如果事务先 UPDATE 再 SELECT,那 SELECT 才是视图生成点。
- 显式用
START TRANSACTION WITH CONSISTENT SNAPSHOT可以让事务一开启就立即建ReadView -
READ COMMITTED级别下,每次SELECT都新建ReadView,所以不保证可重复读 - 长事务中若长时间没读,
m_ids列表可能包含大量已提交但未清理的事务 ID,影响版本遍历效率
版本链 + DB_TRX_ID 判断决定数据可见性
每次读行时,InnoDB 不直接返回最新值,而是顺着 DB_ROLL_PTR 往 undo log 版本链上找,逐个比对每个版本的 DB_TRX_ID 和当前事务的 ReadView 规则:
- 若版本
DB_TRX_ID == creator_trx_id→ 自己改的,可见 - 若
DB_TRX_ID < min_trx_id→ 该版本事务在ReadView创建前已提交,可见 - 若
DB_TRX_ID在m_ids中 → 当时活跃,未提交,不可见 - 若
DB_TRX_ID >= max_trx_id→ 该版本事务在ReadView创建后才开始,不可见
只有满足任一可见条件的版本才会被返回。所以即使其他事务提交了新版本,只要它的 DB_TRX_ID 落在 m_ids 或大于 max_trx_id,就不会被当前事务看到。
快照读和当前读必须分清,混用会破环一致性
SELECT 默认是快照读,走 MVCC;但一旦加锁或写操作,就强制变成当前读,绕过 ReadView 直接读最新版——这是不可重复读重现的最常见原因。
-
SELECT ... FOR UPDATE或SELECT ... LOCK IN SHARE MODE→ 当前读,看到新数据 -
UPDATE/DELETE/INSERT→ 当前读,且会对匹配行加锁 - 典型陷阱:事务中先
SELECT id=1得到value=10,中间别人改成20并提交,再执行UPDATE t SET value = value + 5 WHERE id = 1→ 这条UPDATE是当前读,读到的是20,最终写入25,逻辑错乱
undo log 保留时长直接影响 MVCC 行为
MVCC 能工作,前提是旧版本还在 undo log 里。如果事务太长、或 innodb_max_undo_log_size 设置过小、或 purge 线程跟不上,旧版本可能被清理掉,导致 ReadView 找不到符合条件的历史版本,此时 InnoDB 会报错或降级行为(如返回空或抛出 Snapshot too old 类错误)。
生产环境要注意:
- 避免超长事务(尤其含大查询或休眠),否则 undo log 持续增长且无法 purge
-
innodb_purge_threads建议设为 ≥2,加快历史版本清理 - 监控
Innodb_history_list_length,值持续 > 10000 说明 purge 压力大
真正容易被忽略的,不是 ReadView 怎么建,而是 undo log 是否还活着——版本没了,MVCC 就只是个摆设。


















