快照读基于事务启动时固定Read View读历史版本且不加锁,当前读跳过MVCC读最新已提交数据并加锁;二者机制不同导致结果差异是设计使然,并非bug。

因为快照读和当前读根本不是同一种机制:前者基于事务启动时固定的 Read View 读历史版本,不加锁;后者跳过 MVCC,直接读最新已提交数据并加锁——结果不同是设计使然,不是 bug。
快照读只认事务启动那一刻的 Read View
在 RR 级别下,事务第一次执行普通 SELECT 时就生成一个 Read View,之后所有快照读都复用它。哪怕其他事务插入并提交了新行,只要其 TRX_ID 大于该 Read View 的 up_limit_id,就不会被看到。
- 它不感知新插入、不加锁、也不阻塞别人写
-
SELECT * FROM t WHERE id > 10在事务内执行三次,结果一定相同 - 但这个“一致”只对快照读有效,和真实数据状态无关
当前读强制穿透 MVCC,读最新+加锁
任何触发当前读的语句(如 SELECT ... FOR UPDATE、UPDATE、DELETE)都会忽略 Read View,直接访问聚簇索引中最新版本,并立即加锁。
- 它必须看到其他事务已提交的新行,否则无法正确加锁
- 是否能锁住插入间隙,取决于查询是否命中索引、以及条件类型:
WHERE id = 100只加记录锁;WHERE id > 100才可能激活Next-Key Lock - 如果
name字段没索引,SELECT ... FOR UPDATE WHERE name = 'x'实际只锁匹配行,不锁间隙,幻读必然发生
混用快照读和当前读是幻读高发场景
同一个事务里先做一次快照读拿到旧结果,再执行当前读更新,最后又用快照读验证——三次读取的数据来源完全不同,结果自然不一致。
- 典型现象:
SELECT看到 2 行 → 其他事务插入 1 行并提交 →UPDATE ... WHERE age > 20影响 3 行 → 再SELECT还是 2 行 → 改成SELECT ... FOR UPDATE突然变成 3 行 - 这不是隔离失败,而是你主动混合了两种语义:一个要“历史一致性”,一个要“实时可写性”
- 业务逻辑若依赖“查到的值就是能用来更新的值”,就必须全程用当前读,不能靠快照读做判断
真正容易被忽略的是:RR 隔离级别从不承诺“范围级一致性快照”。它只保证单行多次快照读不变,而防不住当前读暴露的新行——能否锁住间隙,最终取决于索引设计和语句写法,不是隔离级别自动兜底的。


















