快照读读的是undo log中按readview可见性规则筛选出的历史版本数据,而非聚簇索引最新记录或缓冲池脏页;它不访问行记录头lock_bit字段,故全程无需加锁。

快照读不需要锁,是因为它根本没去碰正在被修改的那行数据——它读的是 undo log 里存着的历史版本,不是聚簇索引上带 lock_bit 的最新记录。
快照读到底读的是哪块内存/磁盘?
不是最新行记录,也不是缓冲池里刚刷进来的脏页,而是从 undo log 链中按 ReadView 可见性规则捞出来的历史版本。InnoDB 在执行普通 SELECT 时,只做两件事:查当前事务 ID 是否在 ReadView 的 trx_ids 列表里、比对 min_trx_id 和 max_trx_id,然后顺着 undo 版本链往上找。整个过程不访问行记录头,自然不读 lock_bit 字段,也就完全绕开了锁系统。
常见错误现象:SELECT * FROM t WHERE id = 1 在事务 A 中执行,同时事务 B 正在 UPDATE 同一行但未提交;A 仍能立刻返回结果,且是旧值——这不是“跳过锁”,而是压根没走到加锁那条路径上。
RR 级别下 ReadView 复用如何影响快照一致性?
RR 是事务第一次 SELECT 时生成 ReadView,之后所有快照读都复用它。这意味着:
- 事务内多次
SELECT看到的行版本一致,哪怕其他事务已提交新版本 - 新插入的行如果
TRX_ID大于该ReadView的up_limit_id,就不可见——所以范围查询结果“不变” - 但这种“不变”仅对快照读有效;一旦混入
SELECT ... FOR UPDATE,立刻切换到当前读,看到新数据并加锁
性能影响很实在:大量并发 SELECT 不阻塞 UPDATE,也不被阻塞;但长事务不提交会导致 undo log 无法 purge,空间持续增长,甚至触发 Undo Log Full 错误。
哪些 SELECT 实际上不是快照读?
表面是 SELECT,但只要带锁提示或隐含修改意图,InnoDB 就强制走当前读路径,必须加锁:
-
SELECT ... FOR UPDATE→ 加 X 锁 -
SELECT ... LOCK IN SHARE MODE→ 加 S 锁 -
UPDATE、DELETE、INSERT内部都会先执行一次当前读,再操作
这些操作跳过 MVCC,直接读聚簇索引最新已提交版本,并立即加 record lock 或 next-key lock。误以为“带 WHERE 的 SELECT 都是快照读”,是线上死锁和幻读问题的常见根源。
真正容易被忽略的是:RR 隔离级别不承诺“范围级一致性快照”。它只保证单行多次快照读不变,而防不住当前读暴露的新行——能否锁住间隙,最终取决于索引是否存在、条件是否能命中索引,不是隔离级别自动兜底的。











