当前读与快照读的区别
概述
在 MySQL InnoDB 中,SELECT 查询可以分为两种模式:快照读(Snapshot Read) 和 当前读(Current Read)。理解两者的区别,是深入理解 MVCC、锁机制和事务隔离级别的关键。
核心区别速览
| 对比维度 | 快照读 | 当前读 |
|---|---|---|
| 读取内容 | 历史版本(快照) | 最新已提交版本 |
| 是否加锁 | ❌ 不加锁 | ✅ 加锁(读锁或写锁) |
| 性能 | 高(无锁竞争) | 较低(有锁等待) |
| 使用的 SQL | 普通 SELECT | SELECT FOR UPDATE / LOCK IN SHARE MODE / UPDATE / DELETE |
| 一致性保证 | MVCC 快照一致性 | 锁保证的数据一致性 |
快照读(Snapshot Read)
定义
快照读是指从 MVCC 提供的一致性快照中读取数据,而不是直接从数据行中读取最新值。
适用语句
-- 所有普通的 SELECT 都是快照读
SELECT * FROM user WHERE id = 1;
SELECT * FROM user WHERE age > 20;
SELECT COUNT(*) FROM order;
工作原理
快照读流程
1. 事务中第一次 SELECT → 创建 Read View
2. 读取数据时,根据 DB_TRX_ID 和 Read View 判断可见性
3. 对当前事务不可见的版本,通过 Undo Log 回溯到可见版本
4. 返回可见版本的数据
5. 过程中不加任何锁
特点
- 读不阻塞写:其他事务可自由修改数据
- 写不阻塞读:其他事务修改数据,不影响你的读取
- 一致性:同一事务内多次读取结果相同(REPEATABLE READ 下)
当前读(Current Read)
定义
当前读是指直接读取数据行的最新已提交版本,并对其加锁,防止其他事务并发修改。
适用语句
-- 显式加锁的 SELECT
SELECT * FROM user WHERE id = 1 FOR UPDATE; -- 排他锁(X 锁)
SELECT * FROM user WHERE id = 1 LOCK IN SHARE MODE; -- 共享锁(S 锁)
SELECT ... FOR SHARE; -- MySQL 8.0 语法
-- DML 语句也是当前读
UPDATE user SET name = '张三' WHERE id = 1; -- 先读后写,读时加 X 锁
DELETE FROM user WHERE id = 1; -- 先读后删,读时加 X 锁
INSERT INTO user ...; -- 隐式加锁
工作原理
当前读流程
1. 读取数据行的最新版本(忽略 MVCC 版本链)
2. 根据语句类型加锁
- FOR UPDATE → 加排他锁(X 锁)
- FOR SHARE → 加共享锁(S 锁)
- UPDATE/DELETE → 先加锁,后修改
3. 如果锁被其他事务持有,则等待锁释放
4. 锁释放后,继续执行
特点
- 读到最新值:忽略版本链,直接读最新的已提交数据
- 加锁:防止并发修改
- 可能阻塞:锁冲突时需等待
- 防止丢失更新:保证数据修改的安全性
实际执行差异示例
场景:账户余额查询与更新
-- 表:account(id, name, balance)
-- 初始:id=1, name='Alice', balance=100
-- 事务 A(开始)
BEGIN;
-- ① 快照读:不阻塞,读到旧值
SELECT balance FROM account WHERE id = 1;
-- → 100
-- 事务 B 把 balance 改为 50 并提交
-- (事务 B 的 UPDATE 是当前读,修改成功)
-- ② 快照读:仍然读快照版本
SELECT balance FROM account WHERE id = 1;
-- → 100(不变,MVCC 保证)
-- ③ 当前读:读最新版本
SELECT balance FROM account WHERE id = 1 FOR UPDATE;
-- → 50(最新已提交版本!)
COMMIT;
关键观察
同一个事务中,快照读和当前读可能返回不同的结果:
BEGIN;
SELECT * FROM user WHERE age > 20;
-- → 3 行(快照读)
-- 事务 B 插入新记录并提交
SELECT * FROM user WHERE age > 20;
-- → 3 行(快照读,看不到新记录)
SELECT * FROM user WHERE age > 20 FOR UPDATE;
-- → 4 行(当前读,能看到最新数据)
加锁范围的区别
快照读
| 隔离级别 | 加锁行为 |
|---|---|
| READ UNCOMMITTED | 不加锁 |
| READ COMMITTED | 不加锁 |
| REPEATABLE READ | 不加锁 |
| SERIALIZABLE | ❌ 快照读退化为当前读:自动转 LOCK IN SHARE MODE |
当前读
| 隔离级别 | 加的锁 |
|---|---|
| READ UNCOMMITTED | Record Lock |
| READ COMMITTED | Record Lock |
| REPEATABLE READ | Next-Key Lock(记录锁 + 间隙锁) |
| SERIALIZABLE | Next-Key Lock |
选择建议
何时用快照读
- 查询报表、统计数据
- 不需要实时最新值的读操作
- 希望避免锁冲突的高并发读
何时用当前读
- 需要确保数据未被其他事务修改(常配合后续更新操作)
- 实现悲观锁(SELECT FOR UPDATE)
- 需要读到最新数据进行决策
- 防止丢失更新(需要锁定数据后修改)
面试要点
- 本质区别:快照读读历史版本(不加锁),当前读读最新版本(加锁)
- SQL 区分:普通 SELECT = 快照读;带锁的 SELECT 和 DML = 当前读
- MVCC 关联:快照读依赖 MVCC,当前读依赖锁
- 容易混淆:SERIALIZABLE 下普通 SELECT 也被转化为当前读
- 一致性问题:快照读和当前读混用可能导致”数据不一致”假象,需要理解其原理
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END


暂无评论内容