在 MySQL InnoDB 中,当前读和快照读是 MVCC 机制下的两种数据读取方式,核心区别在于是否读取最新版本、是否加锁、是否受其他事务影响,二者分工协作实现了 “读写不阻塞” 的高效并发。
一、快照读(Snapshot Read)
核心定义:读取数据的历史版本快照,不走锁机制,完全基于 MVCC 实现。
- 本质:读的是 Undo Log 中存储的历史版本,而非数据页的最新版本。
- 加锁情况:无锁,不会阻塞任何写操作,也不会被写操作阻塞。
- 适用场景:普通的
SELECT查询(不加任何锁定子句)。 - 隔离级别影响:
- 读已提交(RC):每次
SELECT都会生成新的 Read View,可能读到其他事务已提交的最新版本(不可重复读)。 - 可重复读(RR):整个事务内共用第一次
SELECT生成的 Read View,保证多次读取结果一致。
- 读已提交(RC):每次
生活化比喻:你去图书馆查资料,不想等别人归还原版书,直接拿了一本影印版(历史快照)阅读,影印版内容不受原版后续修改影响。
-- 这是典型的快照读 SELECT name FROM user WHERE id = 1;在之前的事务协作案例中,事务 A 在 T2、T5、T7 执行的SELECT都是快照读,读取的是 Undo Log 里的历史版本(name="张三"),完全不受事务 B 写操作的影响。
二、当前读(Current Read)
核心定义:读取数据的最新版本,且会对读取的记录加锁,保证后续写操作的一致性。
- 本质:读的是数据页的最新版本,需要借助锁机制防止并发冲突。
- 加锁情况:必须加锁,根据操作类型加不同的锁(如行级排他锁、共享锁)。
- 适用场景:所有会修改数据的操作 + 显式加锁的查询,包括:
INSERT/UPDATE/DELETE:修改数据前必须读最新版本,加排他锁(X 锁)。- 显式锁定读:
SELECT ... FOR UPDATE(加排他锁)、SELECT ... LOCK IN SHARE MODE(加共享锁)。
生活化比喻:你要修改图书馆的原版书,必须先拿到原版(最新版本),并且拿到后会给书贴个 “正在修改” 的标签(加锁),防止别人同时修改。
-- 显式加排他锁的当前读,读最新版本 SELECT name FROM user WHERE id = 1 FOR UPDATE; -- UPDATE 操作隐含当前读,先读最新版本再加锁修改 UPDATE user SET name = "王五" WHERE id = 1;在之前的案例中,事务 B 的UPDATE和事务 A 的SELECT ... FOR UPDATE都是当前读,必须读取最新版本(name="李四"),并且加锁防止冲突。
三、当前读 vs 快照读 核心对比表
| 特性 | 快照读 | 当前读 |
|---|---|---|
| 读取版本 | 历史版本(Undo Log 快照) | 最新版本(数据页当前数据) |
| 加锁情况 | 无锁 | 必须加锁(X 锁 / S 锁) |
| 阻塞情况 | 不阻塞写,也不被写阻塞 | 加 X 锁会阻塞其他写操作,被其他 X 锁阻塞 |
| 适用 SQL | 普通SELECT | INSERT/UPDATE/DELETE、SELECT ... FOR UPDATE等 |
| 依赖机制 | MVCC(Read View + Undo Log) | 锁机制 + MVCC(仅读取最新版本,不依赖快照) |
| 隔离级别支持 | 支持 RC、RR | 支持所有隔离级别 |
四、关键补充
为什么当前读不需要 MVCC 快照?当前读的目标是获取最新数据并修改,必须保证数据的 “实时性”,如果读历史版本会导致脏写(比如基于旧版本修改,覆盖了别人的新修改)。因此当前读会跳过 MVCC 的版本链,直接读取最新版本并加锁。
串行化隔离级别下的特殊情况在
SERIALIZABLE(串行化)隔离级别下,普通SELECT也会被升级为当前读(隐式加共享锁),此时不再有快照读,所有操作串行执行,完全靠锁保证一致性,并发性能会大幅下降。