当前读与快照读
当前读与快照读
快照读(Snapshot Read)
快照读:读取的是某个时间点的一致性版本,而不是数据库此刻的最新值。
它依赖 MVCC(多版本并发控制)。每一行数据在 InnoDB 里都隐含两个版本号:创建版本和删除版本。事务开始时会生成一个 Read View(读视图),这个视图决定了你“能看到哪些版本”。
典型的快照读语句:
1 | select * from user where id = 1; |
前提是没有加锁。
它的特点是:
- 不加行锁
- 不阻塞写
- 读的是“历史版本”
你可以把它理解成:数据库给你拍了一张“时间照片”。你之后的普通查询都在看这张照片。
举个例子。
事务A:
1 | begin; |
事务B:
1 | update account set balance = 200 where id = 1; |
事务A再次:
1 | select balance from account where id = 1; |
如果是REPEATABLE READ隔离级别,事务A还是会看到 100。因为它在第一次读时已经确定了“照片时间”。
它读的是undo log里的旧版本,而不是数据页上的最新值。
当前读(Current Read)
当前读:读取的是数据库当前最新版本,并且会对读取的记录加锁。
典型语句:
1 | select ... for update; |
你会发现一个规律:凡是“可能要改数据”的操作,都会用当前读。
因为你要改数据,必须确认你看到的是“现在真实存在的那一行”,否则就会出现逻辑错乱。
比如:
事务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 和锁的边界。
