快照读读的是undo log中保存的历史版本数据,而非聚簇索引上的最新物理记录;它通过事务的readview判断可见性,从版本链中获取对应历史版本,全程不访问行锁字段,故无需加锁。

快照读不加锁,不是因为“选择不加”,而是它根本没机会去加——它压根不碰当前行的最新物理记录。
快照读读的是哪里的数据?
它读的是 undo log 里存的历史版本,不是聚簇索引上正在被修改的那行数据。InnoDB 在执行普通 SELECT 时,只根据事务自己的 ReadView 判断哪个版本可见,然后从 undo log 链中捞出对应版本。这个过程完全绕开了行记录头的 lock_bit 字段,自然不参与锁等待队列。
常见错误现象:事务 A 执行 SELECT * FROM t WHERE id = 1,同时事务 B 正在对同一行做 UPDATE 但未提交;A 仍能立刻返回结果,且是事务开始前的值——这不是脏读,是合法的快照版本。
ReadView 生成时机决定隔离级别行为
ReadView 是快照读的判断依据,但它怎么生成,直接决定了 RR 和 RC 的语义差异:
-
REPEATABLE READ(InnoDB 默认):事务第一次SELECT时生成ReadView,后续所有快照读复用它,保证可重复读 -
READ COMMITTED:每次SELECT都新建ReadView,所以能看到其他事务已提交的新数据
注意:SELECT 是否触发快照读,和隔离级别无关,只和语句本身有关——只要没带 FOR UPDATE 或 LOCK IN SHARE MODE,就走快照读路径。
哪些 SELECT 实际上是当前读?
表面是 SELECT,但只要带锁提示或隐含修改意图,InnoDB 就强制走当前读,必须加锁:
-
SELECT ... FOR UPDATE→ 加X 锁 -
SELECT ... LOCK IN SHARE MODE→ 加S 锁 -
UPDATE、DELETE、INSERT内部都会先执行一次当前读,再操作
这些操作读取的是最新已提交版本,并立即加锁,和 MVCC 无关。误以为“带 WHERE 的 SELECT 都是快照读”,是线上死锁和幻读问题的常见根源。
不加锁的代价藏在 undo log 里
快照读虽不争锁,但依赖 undo log 持续保存历史版本。如果长事务不提交,它的 ReadView 一直有效,导致旧版本无法 purge,undo log 空间持续膨胀,可能触发 Undo Log Full 或拖慢 purge 线程。
另外,二级索引 + 回表场景下,InnoDB 可能需要为多条索引记录构造多个版本,CPU 开销会上升——这点常被忽略,尤其在宽表高频查询时。











