一、快照读

1. 定义
快照读读取的是数据在某个时间点的快照(通常是一个历史版本),这个快照是基于undo logReadView机制实现的。它不会去读取其他未提交事务修改的数据,也不会因此去加锁(除非是在SERIALIZABLE隔离级别下)。

2. 实现原理:MVCC
快照读是实现MySQL MVCC(多版本并发控制) 模型的关键。InnoDB会为每一行数据维护多个版本。当一个事务启动时,它会生成一个当前系统的“快照”(在代码中体现为一个ReadView结构),这个ReadView决定了该事务能看到哪些版本的数据:

  • 已提交的事务所做的修改:可见
  • 未提交的事务或在本事务开始之后才提交的事务所做的修改:不可见

3. 触发语句

  • 普通的SELECT语句(不加锁的情况下)。
    1
    SELECT * FROM table WHERE ...;

4. 特点

  • 不加锁:因此不会阻塞其他事务的写操作(UPDATE, DELETE, INSERT),并发性能高。
  • 读到的可能是历史数据:在可重复读(REPEATABLE-READ) 隔离级别下,一个事务内多次快照读都会读取到同一个时间点的数据,从而实现可重复读。在读已提交(READ-COMMITTED) 隔离级别下,每次快照读都会生成新的ReadView,所以每次都能读到最新已提交的数据。

二、当前读

1. 定义
当前读读取的是数据的最新版本,并且会保证在读取过程中,其他事务无法修改这个数据,直到当前读事务结束。这是通过加锁来实现的。

2. 实现原理:锁
当前读通过给记录加锁(如行锁、间隙锁)来确保数据的一致性。它读取的是已提交的最新数据,并且在读取时防止其他事务并发修改,从而避免了脏读和不可重复读。

3. 触发语句
所有会导致加锁的读取操作都是当前读:

  • SELECT ... FOR UPDATE:加排他锁(X锁)。
  • SELECT ... LOCK IN SHARE MODE:加共享锁(S锁)。(在MySQL 8.0中,更推荐使用SELECT ... FOR SHARE
  • UPDATE ...:更新数据前,会先进行一次当前读,读取最新数据然后加锁。
  • DELETE ...:同上。
  • INSERT ...:插入时也会涉及特殊的锁(如插入意向锁)。

4. 特点

  • 加锁:会对读取的记录加锁,可能会阻塞其他试图修改这些记录的事务。
  • 读取最新已提交数据:总是能读到所有已提交事务的最新修改结果。
  • 解决幻读:在可重复读(REPEATABLE-READ) 隔离级别下,SELECT ... FOR UPDATE这样的当前读语句会通过间隙锁(Next-Key Locking) 来锁住一个范围,防止其他事务在这个范围内插入新数据,从而解决了幻读问题。

三、核心区别对比表

特性 快照读 当前读
核心概念 读取数据的历史快照 读取数据的最新版本
实现机制 MVCC(多版本并发控制) (行锁、间隙锁等)
是否加锁 (无锁读,并发性好) (加共享锁或排他锁)
数据来源 undo log中的历史版本 数据页中的最新记录
触发语句 普通 SELECT 语句 SELECT ... FOR UPDATE, SELECT ... LOCK IN SHARE MODE, UPDATE, DELETE, INSERT
隔离级别影响 RR级别下,同一事务内多次读取结果一致;RC级别下,每次读取最新快照 在所有隔离级别下行为基本一致,都读取最新数据并加锁

四、一个简单的例子来帮助理解

假设有一张表 account,有一条数据:id = 1, balance = 1000

  1. 时间点 T1:事务A开启,执行一个快照读:

    1
    2
    3
    -- 事务A
    START TRANSACTION;
    SELECT balance FROM account WHERE id = 1; -- 快照读,读到 1000
  2. 时间点 T2:事务B开启,并修改了这条数据,将余额减去100,然后提交。

    1
    2
    3
    4
    -- 事务B
    START TRANSACTION;
    UPDATE account SET balance = balance - 100 WHERE id = 1; -- 当前读,加锁,读到最新值1000,更新为900
    COMMIT; -- 提交
  3. 时间点 T3:事务A再次执行快照读:

    1
    2
    -- 事务A(仍在之前的事务中)
    SELECT balance FROM account WHERE id = 1; -- 快照读,仍然读到 1000(可重复读)
  4. 时间点 T4:事务A执行一个当前读:

    1
    2
    -- 事务A
    SELECT balance FROM account WHERE id = 1 FOR UPDATE; -- 当前读,加锁,读到最新已提交的数据 900

总结一下这个例子:

  • 事务A内的两次快照读结果相同(都是1000),体现了“可重复读”。
  • 事务B的更新操作是一个当前读,它读取了最新的值(1000)并进行修改。
  • 事务A最后的当前读FOR UPDATE)绕开了MVCC的快照,直接读取了最新的已提交数据(900)。

结论

  • 当你需要高并发查询,且对实时性要求不高(允许读到历史数据)时,使用快照读(普通SELECT)。
  • 当你需要确保读取的数据是最新的,并且在你处理的过程中不允许别人修改(例如电商库存扣减、余额检查)时,使用当前读SELECT ... FOR UPDATE)。