Skip to content

一致性视图 (Read View) 与当前读 (Current Read)

一、 问题的提出:隔离性与锁的“矛盾”

  • 可重复读 (Repeatable Read, RR):理论上,事务启动时创建快照 (Read View),后续查询看到的数据不受其他事务修改的影响,仿佛是“隔离”的。
  • 行锁现象:实际上,当一个事务要更新某行时,如果该行被其他事务锁定,当前事务会被阻塞等待,表现出“不隔离”的一面。
  • 核心疑问:当等待结束后,进行更新操作时,它读到的数据到底是快照里的旧数据,还是其他事务修改后的新数据?

二、 核心机制:MVCC 与 Read View

  1. MVCC (多版本并发控制):InnoDB 实现事务隔离的关键。

    • 行版本 (Row Version):每行数据可能有多个版本,每次更新都会生成新版本。
    • row trx_id:每个数据版本都关联一个事务 ID,记录是哪个事务修改了它。
    • Undo Log:旧版本数据并不物理存储,而是通过当前版本和 Undo Log 链计算出来。
  2. 一致性读视图 (Consistent Read View)

    • 作用:定义事务在执行期间“能看到哪个版本的数据”,实现“快照”功能。它是一个逻辑快照,不是物理拷贝。
    • 创建时机
      • 可重复读 (RR):事务 启动后第一个操作 InnoDB 表的语句 执行时创建,整个事务期间 复用 这一个 Read View。(或使用 START TRANSACTION WITH CONSISTENT SNAPSHOT 立即创建)。
      • 读提交 (RC)每条 SELECT 语句执行前 都会 重新创建 一个 Read View。
    • 结构:包含事务创建 Read View 时:
      • 当前所有活跃 (已启动但未提交) 事务 ID 的列表。
      • 活跃事务 ID 列表中的最小值 (低水位)。
      • 系统已创建过的事务 ID 的最大值 + 1 (高水位)。
  3. 数据版本可见性规则 (对于一个事务的 Read View 来说):

    • 自身更新:总是可见。
    • 其他事务的更新 (判断数据版本的 row trx_id):
      1. row trx_id < 低水位 (或不在活跃列表且 ≤ Read View 创建时的最大事务 ID):表示该版本在 Read View 创建前已提交,可见 (绿色区域)。
      2. row trx_id ≥ 高水位:表示该版本是 Read View 创建后才启动的事务生成的,不可见 (红色区域)。
      3. 若 低水位 ≤ row trx_id < 高水位:
        • 如果 row trx_id 活跃事务列表中:表示该版本是由 Read View 创建时还未提交的事务生成的,不可见 (黄色区域 - 情况 a)。
        • 如果 row trx_id 不在活跃事务列表中:表示该版本是由 Read View 创建时已提交的事务生成的,可见 (黄色区域 - 情况 b)。
    • 简化规则 (除自身更新外):
      1. 版本未提交,不可见。
      2. 版本已提交,但在视图创建之后提交,不可见。
      3. 版本已提交,且在视图创建之前提交,可见。

三、 两种读取方式

  1. 一致性读 (Consistent Read)

    • 使用者:普通的 SELECT 语句。
    • 行为:基于事务当前的 Read View数据版本可见性规则,去查找并返回符合条件的、对本事务可见 的那个版本的数据。
    • 效果:在 RR 级别下保证了可重复读。如例子中,事务 A 的 SELECT 根据其启动时创建的 Read View,最终看到了 k=1 的版本。
  2. 当前读 (Current Read)

    • 使用者
      • UPDATE
      • DELETE
      • INSERT
      • SELECT ... LOCK IN SHARE MODE (加共享锁)
      • SELECT ... FOR UPDATE (加排他锁)
    • 行为总是 读取记录的 最新已提交版本,并对记录 加锁 (S 锁或 X 锁)。
    • 原因:更新操作必须基于最新数据进行,否则会丢失其他事务已提交的修改 (Lost Update)。
    • 与锁的结合
      • 如果最新版本没有被锁定,则直接读取并加锁。
      • 如果最新版本被其他未提交事务锁定 (根据两阶段锁协议,锁在事务结束时释放),则当前读操作会被 阻塞,进入锁等待,直到锁被释放。
      • 等待结束后,重新进行当前读,获取此刻的最新已提交版本并加锁。
    • 效果:解释了例子中事务 B 为何能读到事务 C 提交的 k=2 并更新为 k=3。它的 UPDATE 语句执行的是当前读,读到了最新的已提交值 (1,2),然后在此基础上 +1。其后的 SELECT 因为是读自己的更新,也看到了 k=3

四、 隔离级别对比 (RC vs RR)

  • 核心区别:Read View 的创建时机不同。
  • RR:事务启动时创建一次 Read View,后续所有一致性读共享此视图。
  • RC:每条 SELECT 语句执行前都创建新的 Read View。
  • 导致现象:在 RC 下,事务内的多次查询可能读到不同的数据(如果其他事务在此期间提交了更新),无法保证可重复读。如例子中,RC 下事务 A 的 SELECT 会看到 k=2

五、 总结

  • 事务的隔离性主要通过 MVCCRead View 实现 一致性读 来保障(尤其在 RR 级别)。
  • 更新、删除、加锁读等操作执行的是 当前读,需要读取并锁定最新已提交版本,这是为了数据一致性 (避免丢失更新) 和 并发控制 (加锁)。
  • “隔离”体现在普通查询 (一致性读) 上,而“不隔离” (等待锁) 体现在更新和加锁读 (当前读) 上。 两者遵循不同的规则,服务于不同的目的,共同构成了 InnoDB 的事务处理机制。
  • 表结构变更 (DDL) 不使用 MVCC,通常遵循当前读逻辑,无法实现可重复读。