MVCC - undo log 版本链数据访问规则

课程视频中列出的 undo log 版本链数据访问规则有误,其中第 3 条和第 4 条应改为如下:

下面举例说明不同规则的触发情况

第 1 条规则和第 2 条规则

现在表中存在一条记录:

id age name db_trx_id db_roll_ptr
30 30 A30 1 null

接下来,数据库进行了事务操作,此时隔离级别是 RC 或 RR

事务2 事务3
开启事务 开启事务
修改 age 为 3
(1) 查询记录,生成 readview2
(2) 查询记录,生成 readview1
提交事务 提交事务

在查询 (1) 和 (2) 执行时,生成的 undo log 版本链以及 readview 如下:

addr id age name db_trx_id db_roll_ptr
> 30 3 A30 2 0x01
0x01 30 30 A30 1 null
readview1 readview2
m_ids 2,3 2,3
min_trx_id 2 2
max_trx_id 4 4
creator_trx_id 2 3

先来分析事务 3 的查询 (1) 的情况,它是基于 readview2:

  • 首先来到版本链中最新记录:trx_id = 2,此时 4 条规则都无法满足
    • trx_id == creator_trx_id: 2 == 3 不成立
    • trx_id < min_trx_id: 2 < 2 不成立
    • trx_id >= max_trx_id: 2 >= 4 不成立
    • min_trx_id <= trx_id < max_trx_id 且 trx_id not in m_ids: 2 <= 2 < 4 成立,但 2 not in (2,3) 不成立
  • 然后来到版本链中第一条旧记录:trx_id = 1,此时第 2 条规则满足
    • trx_id < min_trx_id: 1 < 2 成立,将返回 trx_id 为 1 的记录

因此事务 3 的查询 (1) 查到的记录,age = 30,符合预期,它无法看到事务 2 修改的 age = 3

再来看事务 2 的查询 (2) 的情况,它是基于 readview1:

  • 首先来到版本链中最新记录:trx_id = 2,此时第 1 条规则满足
    • trx_id == creator_trx_id: 2 == 2 成立,将返回 trx_id 为 2 的记录

因此事务 2 的查询 (2) 查到的记录,age = 3,符合预期,它可以看到自己刚刚的修改

第 3 条规则

现在表中存在一条记录:

id age name db_trx_id db_roll_ptr
30 30 A30 1 null

接下来,数据库进行了事务操作,此时隔离级别是 RR

事务2 事务3
开启事务
(1) 查询记录,生成 readview1
开启事务
修改 age 为 3
(2) 查询记录,生成 readview2
(3) 查询记录,复用 readview1
提交事务 提交事务

在查询 (2) 和查询 (3) 执行时,生成的 undo log 版本链以及 readview 如下:

addr id age name db_trx_id db_roll_ptr
> 30 3 A30 3 0x01
0x01 30 30 A30 1 null
readview1 readview2
m_ids 2 2,3
min_trx_id 2 2
max_trx_id 3 4
creator_trx_id 2 3

查询 (3) 执行时,复用查询 (1) 生成的 readview1

直接来分析事务 2 的查询 (3) 的情况,它是基于 readview1:

  • 首先来到版本链中最新记录:trx_id = 3,此时第 3 条规则满足
    • trx_id >= max_trx_id: 3 >= 3 成立,但是第 3 条规则满足是反逻辑,表示去找版本链中更旧的记录
  • 然后来到版本链中第一条旧记录:trx_id = 1,此时第 2 条规则满足
    • trx_id < min_trx_id: 1 < 2 成立,将返回 trx_id 为 1 的记录

因此事务 2 的查询 (3) 查到的记录,age = 30,符合预期,它无法看到事务 3 修改的 age = 3

第 4 条规则

现在表中存在一条记录:

id age name db_trx_id db_roll_ptr
30 30 A30 1 null

接下来,数据库进行了事务操作,此时隔离级别是 RC 或 RR

事务2 事务3
开启事务 开启事务
修改 age 为 3
(1) 查询记录,生成 readview2
提交事务
(2) 查询记录,生成 readview1
提交事务

在查询 (1) 和查询 (2) 执行时,生成的 undo log 版本链以及 readview 如下:

addr id age name db_trx_id db_roll_ptr
> 30 3 A30 3 0x01
0x01 30 30 A30 1 null
readview1 readview2
m_ids 2 2,3
min_trx_id 2 2
max_trx_id 4 4
creator_trx_id 2 3

直接来分析事务 2 的查询 (2) 的情况,它是基于 readview1:

  • 首先来到版本链中最新记录:trx_id = 3,此时第 4 条规则满足
    • min_trx_id <= trx_id < max_trx_id 且 trx_id not in m_ids: 2 <= 3 < 4,且 3 not in (2),将返回 trx_id 为 3 的记录

因此事务 2 的查询 (2) 查到的记录,age = 3,符合预期:

  • 对于 RC 的情况,事务 3 修改的 age = 3 已经提交,事务 2 可以读已提交
  • 对于 RR 的情况,事务 2 是第一次做快照读,因此也可以读到事务 3 的修改

总结

个人感觉其实以上 4 条规则可以简化如下:

  • trx_id == creator_trx_id:表示如果 trx_id 对应的记录是当前事务修改的,那么当前事务可以看到自己的最近一次更新的记录。
  • trx_id < max_trx_id && trx_id not in m_ids:表示当前事务可以看到在 readview 生成之前已开启的事务所提交的 undolog 记录的最新的那条记录
    • trx_id < max_trx_id:只关注在 readview 生成之前已开启的事务。trx_id >= max_trx_id 的情况应该仅在 RR 隔离级别下才会出现
    • trx_id not in m_ids:trx_id 记录所属的事务不再活跃,说明该记录在当前事务的 readview 生成前,就已经提交了,那么当前事务自然可以读到这条已提交的记录

不满足以上规则的,都继续找更旧版本的记录。