当前读与快照读

快照读(Snapshot Read)

快照读:读取的是某个时间点的一致性版本,而不是数据库此刻的最新值。

它依赖 MVCC(多版本并发控制)。每一行数据在 InnoDB 里都隐含两个版本号:创建版本和删除版本。事务开始时会生成一个 Read View(读视图),这个视图决定了你“能看到哪些版本”。

典型的快照读语句:

1
select * from user where id = 1;

前提是没有加锁。

它的特点是:

  • 不加行锁
  • 不阻塞写
  • 读的是“历史版本”

你可以把它理解成:数据库给你拍了一张“时间照片”。你之后的普通查询都在看这张照片。

举个例子。

事务A:

1
2
begin;
select balance from account where id = 1; -- 假设查到100

事务B:

1
2
update account set balance = 200 where id = 1;
commit;

事务A再次:

1
select balance from account where id = 1;

如果是REPEATABLE READ隔离级别,事务A还是会看到 100。因为它在第一次读时已经确定了“照片时间”。

它读的是undo log里的旧版本,而不是数据页上的最新值。


当前读(Current Read)

当前读:读取的是数据库当前最新版本,并且会对读取的记录加锁。

典型语句:

1
2
3
4
5
select ... for update;
select ... lock in share mode;
update
delete
insert

你会发现一个规律:凡是“可能要改数据”的操作,都会用当前读。

因为你要改数据,必须确认你看到的是“现在真实存在的那一行”,否则就会出现逻辑错乱。

比如:

事务A:

1
select balance from account where id = 1 for update;

这个操作会:

1)读最新版本
2)加排他锁(X锁)
3)阻塞其他事务对这行的修改

它不是看历史版本,它是强行拉到“现实世界”。


核心区别对比

用一句话总结:

快照读 = 看历史版本,不加锁
当前读 = 看最新版本,加锁

再深入一点:

快照读靠 MVCC 实现一致性
当前读靠锁实现一致性

快照读解决的是:读-写并发问题
当前读解决的是:写-写并发问题


为什么需要两个机制?

这是个很有意思的设计问题。

如果所有读都加锁,会发生什么?

  • 大量读阻塞写
  • 吞吐量暴跌
  • 数据库退化成串行执行

如果所有读都不加锁,会发生什么?

  • update 时不知道你改的是哪个版本
  • 丢失更新
  • 幻读失控

所以 InnoDB 做了一个非常聪明的折中:

  • 普通查询用 MVCC(乐观并发)
  • 涉及修改的读用加锁(悲观并发)

它不是非黑即白,而是“按场景切换武器”。


和隔离级别的关系

REPEATABLE READ下:

  • 快照读使用第一次生成的Read View
  • 当前读每次都读最新版本

这点非常关键。

所以你可能看到一个“现象”:

事务里普通select查到 100
select for update查到 200

这不是数据库精神分裂。
是你用的是不同的读模型。


一个容易被误解的点

有人会问:

既然有 MVCC,为什么还需要 next-key lock?

因为 MVCC 只能解决“行版本可见性”,
不能解决“范围插入”的问题(幻读)。

举个例子:

1
select * from user where age between 10 and 20 for update;

这是当前读。

它会加 next-key lock,锁住区间,防止别人往这个范围插数据。

MVCC 做不到这一点,因为新插入的记录根本没有“旧版本”。

这就是 MVCC 和锁的边界。