当前读与快照读的区别

当前读与快照读的区别

概述

在 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)
  • 需要读到最新数据进行决策
  • 防止丢失更新(需要锁定数据后修改)

面试要点

  1. 本质区别:快照读读历史版本(不加锁),当前读读最新版本(加锁)
  2. SQL 区分:普通 SELECT = 快照读;带锁的 SELECT 和 DML = 当前读
  3. MVCC 关联:快照读依赖 MVCC,当前读依赖锁
  4. 容易混淆:SERIALIZABLE 下普通 SELECT 也被转化为当前读
  5. 一致性问题:快照读和当前读混用可能导致”数据不一致”假象,需要理解其原理
© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容