隐藏字段作用
下面,我们通过下面的表来表示三个隐藏字段工作的原理:
用户表user,事务A执行插入张三数据之后:
id | name | age | db_trx_id | db_roll_ptr |
1 | 张三 | 20 | 1 | null(刚插入,无回滚地址) |
用户表user,事务B执行修改张三年龄数据之后:
id | name | age | db_trx_id | db_roll_ptr |
1 | 张三 | 30 | 2 | 0x10010001(假设这是张三之前的地址) |
undolog:
事务A执行之后undolog内产生一条新纪录
id | name | age | db_trx_id | db_roll_ptr |
1 | 张三 | 20 | 1 | null |
事务B执行之后undolog内产生一条新纪录
id | name | age | db_trx_id | db_roll_ptr |
1 | 张三 | 30 | 2 | 0x10010001 |
如果还有其他的事务操作这条数据,以此类推即可。
ReadView
在事务执行的时候,就会生成当前事务的ReadView,用于保存当前事务之前活跃的所有事务id,之前活跃的事务的最小id,当前事务结束后即将分配的下一个id,创建ReadView的当前事务id,名字分别如下:
m_ids: 截止到当前事务id之前,所有的活跃事务id
min_trx_id: 记录以上活跃事务id中的最小id值
max_trx_id: 保存当前事务结束后应分配的下一个id值
creator_trx_id: 保存创建ReadView的当前事务的id
我们用几张表来表示下三者结合的工作过程:
给出一个事务操作后形成的用户表:
事务1插入张三
事务2插入李四
id | name | age | db_trx_id | db_roll_ptr |
1 | 张三 | 20 | 1 | null |
2 | 李四 | 25 | 2 | null |
模拟并发事务的工作过程:
时刻 | 事务A id:8 | 事务B id:9 |
t1 | begin | |
t2 | beigin | |
t3 | 查询张三的年龄为20 | |
t4 | 修改张三的年龄为30 | |
t5 |
commit | |
t6 | 再次查询张三的age,MVCC下应为20 |
|
t7 | commit |
事务AB工作时会分别生成自己的ReadView:
事务A的ReadView:
m_ids |
min_trx_id | max_trx_id | creator_trx_id |
假设为3,4,5,6 | 3 | 9(下一个应分配事务id) | 8(当前事务id) |
所有的select不加锁,所以都是执行的快照读,所以后面的事务可以从undolog中读取到之前的事务执行的状态,所以在做查询时必须参考之前的快照。
现在开始分析事务8和9每个时刻的操作。
在t3时刻,查询张三的年龄时:
先去看这条数据是否在m_ids中,若果在,则处于活跃状态,说明这条数据还没提交,则不能访问,若不在,说明在当前事务之前已经提交,则可以访问,接着去查找创建这条数据的事务id是否小于当前事务id,如果小于,那一定是在当前事务之前已经执行完的事务,就可以读取到这条数据,否则,还未执行,不可访问。
上面这段话比较绕,但说的比较详细,如果理解的话,那么事务9的执行过程就很清晰了。
在t4时刻,修改用户表的年龄为30:
undolog产生第一条快照数据
id | name | age | db_trx_id | db_roll_ptr |
1 | 张三 | 20 | 1 | null |
事务9执行修改操作之后,用户表应为:
id | name | age | db_trx_id | db_roll_ptr |
1 | 张三 | 30 | 9 | 0x10010001 |
2 | 李四 | 25 | 2 | null |
张三产生回滚地址,当前事务id变化。
t5时刻提交:
事务9生效。
t6时刻再次查询张三的age:
此时拿最后一次执行的事务id-9去ReadView中去m_ids里找,找得到说明时活跃的,没有提交,活跃时不可访问,如果和creator_trx_id比较,相等,说明是自己的操作,可以访问,否则无法访问。如果不在m_ids,说明不再活跃,已提交,不再和当前创建事务id做比较,去和max_trx_id对比,如果db_trx_id大于等于max_trx_id,说明查询的数据在当前事务之后发生改变,无法访问,此时需要通过undolog快照去查找db_trx_id为当前事务id的那条数据,根据undolog表可知,张三的age为20。
这就是MVCC的实现过程,下面用文字来描述下隔离级别的实现:
事务访问数据库时,先判断trx_id是否在m_ids里面
如果在,说明事务是活跃的,继续判断trx_id于ReadView中createor_trx_id的关系
相等,说明当前事务再访问自己的操作,可以访问;
不等,说明当前事务访问的是其他活跃的未提交事务的数据,无法访问。
如果不存在于m_ids中,继续判断trx_id与ReadView中的max_trx_id的关系
若trx_id>=max_trx_id,说明访问的最新的数据是在当前事务后面的操作,无法访问
若trx_id<max_trx_id,说明访问的最新数据是当前事务之前已提交的数据,可以访问
结语
就写到这里吧,关于数据库的数据结构其实还有一些,比如索引,索引的底层B+tree,不过这些东西也很多,准备单独分出来说,咱们下一篇再见。码字不易,觉得还不错,就给个赞吧!