快照读读的是事务启动时ReadView可见的最新已提交历史版本:依据DB_TRX_ID与ReadView.min_trx_id、max_trx_id及活跃事务列表比对可见性,沿undo log版本链定位该行数据。

快照读到底读的是哪一行数据?
快照读不是“随便读一个旧版本”,而是严格依据事务启动时生成的 ReadView,沿着行记录的 DB_ROLL_PTR 指针在 undo log 版本链上逐个比对可见性。关键判断逻辑是:DB_TRX_ID 小于 ReadView.min_trx_id → 已提交且可见;大于等于 ReadView.max_trx_id → 未开始或未来事务,不可见;在两者之间则需检查是否在 ReadView.mins_trx_ids(活跃事务列表)中。
常见错误现象:
- 在
REPEATABLE READ下,事务内第二次SELECT看不到其他事务已提交的更新 —— 这不是 bug,是预期行为:第一次查询就固定了ReadView,后续复用它 - 在
READ COMMITTED下,同一事务内两次SELECT结果不同 —— 因为每次都会新建ReadView,取最新已提交版本
性能影响:快照读不加锁,但需遍历版本链 + 判断可见性,若版本链过长(如大事务长期未提交),可能拖慢查询响应
为什么 SELECT ... FOR UPDATE 一定触发当前读?
因为它的语义是“我要基于最新状态做修改”,必须确保读到的数据没被别的事务覆盖或删除。当前读会跳过 MVCC 的版本筛选逻辑,直接定位到聚簇索引中该行的最新物理记录,并对其加 X 锁(或 S 锁,取决于语句)。
使用场景明确:
-
UPDATE、DELETE、INSERT ... ON DUPLICATE KEY UPDATE底层都隐式执行当前读,否则可能基于过期快照更新,造成数据丢失 -
SELECT ... LOCK IN SHARE MODE是共享锁当前读,适用于“查后只读不改,但要防别人改”的协作场景 - 即使在
READ COMMITTED隔离级别下,FOR UPDATE也绝不会退化为快照读 —— 锁机制优先级高于 MVCC
容易踩的坑:在高并发扣减库存场景中,仅用普通 SELECT 查余额再 UPDATE,会导致超卖;必须用 SELECT ... FOR UPDATE 保证查+改原子性
REPEATABLE READ 和 READ COMMITTED 对快照读的影响差异在哪?
核心区别只在 ReadView 的生成时机,而非“是否启用 MVCC”。两者都依赖 MVCC,但策略不同:
-
REPEATABLE READ:事务第一次执行快照读时生成ReadView,之后整个事务生命周期内复用 —— 所以能解决“不可重复读”,但无法避免“幻读”(间隙锁另说) -
READ COMMITTED:每次执行快照读前都新建ReadView,因此能看到其他事务刚提交的变更 —— 解决了“脏读”,但不保证“不可重复读”
注意:两个级别下,SELECT ... FOR UPDATE 行为完全一致,都是当前读;隔离级别只影响快照读的可见性规则
如何验证某条 SELECT 走的是快照读还是当前读?
不能只看 SQL 文本,得结合执行计划和锁行为判断:
- 看执行计划中的
Extra字段:Using where; Using index通常对应快照读;若出现Using where; Using index; Using MRR或涉及LOCK IN SHARE MODE关键字,则是当前读 - 用
SHOW ENGINE INNODB STATUS\G观察TRANSACTIONS部分,当前读会显示持有行锁(RECORD LOCKS),快照读则无锁信息 - 最直接方式:在事务中执行
SELECT后,另起一个事务尝试UPDATE同一行 —— 若被阻塞,说明前一个SELECT加了锁,即当前读;若成功,大概率是快照读
复杂点在于:某些优化器路径(如覆盖索引+ICP)可能让当前读看起来“没加锁”,但实际仍走最新版本;真正可靠的判断依据永远是“是否加锁”和“是否读最新提交值”,而不是语法表面


















