MVCC - undo log 版本链数据访问规则
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 生成前,就已经提交了,那么当前事务自然可以读到这条已提交的记录
不满足以上规则的,都继续找更旧版本的记录。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 RQTN!