我是如何低效的看TiKV代码的(三)

逃不开躲不掉的mvcc

无论是为了提高事务的吞吐, 还是事务的隔离级别, 我们都逃不开mvcc的概念. 这个概念并非是分布式数据库引入的新概念,MySQL , Postgres数据库就使用了这些技术.
TiKV的源码中, 写操作在txn.rs中使用事务写入, 读操作使用reader.rs中使用MvccReader来获取数据. 上一篇文章中并没有详细的说明TiKV读写数据的具体流程, 就是因为牵扯到MVCC和分布式事务.这次在介绍完MVCC后,会将TiKV的读写过程做一个详细的描述.

什么是MVCC

MVCC是解决并发访问控制的一种技术. 假设在没有MVCC的时候, 当有写操作, 对数据进行上锁, 那么所有的读操作都会被阻塞.
那么解决这个问题的最原始方法就是读写分离, 一个数据有两个版本, 一个是读版本, 一个是写版本.
写操作操作写版本, 写完之后移动到读版本中.

MVCC的方案比这个复杂.

InnoDB实现MVCC的方法是,它存储了每一行的三个额外的隐藏字段,6字节的DB_ROW_ID,6字节的DB_TX_ID,7字节的DB_ROLL_PTR(指向对应回滚段的地址)。

在InnoDB中基本的结构是:

基本结构

事务2执行UPDATE之后


04.png

事务3执行UPDATE之后


05.png

具体的副本创建规则

  1. INSERT 设置当前系统版本号作为DB_ROW_ID; DB_TX_ID是当前的事务ID, DB_ROLL_PTR 是 NULL

  2. UPDATE 先将当前的数据复制到undo log中, 插入一条新记录, DB_TX_ID是当前的事务ID, DB_ROLL_PTR是之前复制到undo log中的地址.

通过这样, 每行的数据的多版本信息通过undo log串联起来. 因此B+树中保存着数据的最新版本,低版本的数据都在undo log中保存.

Read View来解决事务的隔离性

在Innodb中,创建一个新事务的时候,innodb会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),副本中保存的是系统当前不应该被本事务看到的其他事务id列表。
当用户在这个事务中要读取该行记录的时候,innodb会将该行当前的版本号与该read view进行比较。

具体算法

  1. 设该行的当前事务id为trx_id,read view中最早的事务id为up_limit_id, 最新的事务id为low_limit_id
  2. 如果trx_id < up_limit_id的话,那么表明该行记录所在的事务已经在本次新事务创建之前就提交了,所以该行记录的当前值是可见的。跳到步骤6.
  3. 如果trx_id > low_limit_id的话,那么表明该行记录所在的事务在本次新事务创建之后才开启,所以该行记录的当前值不可见.跳到步骤5。
  4. 如果up_limit_id<=trx_id<=low_limit_id, 那么表明该行记录所在事务在本次新事务创建的时候处于活动状态,从up_limit_idlow_limit_id进行遍历,如果trx_id等于他们之中的某个事务id的话,那么不可见。跳到步骤5;如果trx_id不等于其中的任何一个id, 那么数据可见.
  5. 从该行记录的DB_ROLL_PTR指针所指向的回滚段中取出最新的undo log的版本号,将它赋值该trx_id,然后跳到步骤2.
  6. 将该可见行的值返回。

具体示例

时间序列 事务1 事务2 事务3
1 begin
begin
Insert into demo(v) values(1); 假设此时事务号1
Insert into demo(v) values(2); 假设此时事务号2
select * from demo; 此时创建read view, up_limit_id = 1, low_limit_id = 2 活跃事务列表为(2) (注意当前事务不在活跃事务列表中)
Insert into demo(v) values(3); 事务号为3
Insert into demo(v) values(4); 事务号为4
Insert into demo(v) values(5); 事务号为5
select * from demo; 此时创建read view, up_limit_id = 1, low_limit_id = 5; 活跃事务列表为(1,2);因此只能看到3, 4, 5
select * from demo; 此时创建read view, up_limit_id = 1, low_limit_id = 5; 活跃事务列表为(1);因此能看到2, 3, 4, 5
select * from demo; 此时创建read view, up_limit_id = 1, low_limit_id = 2 活跃事务列表为(2) 因此能看到1 (RR隔离级别 read view 创建后在事务内部不会更改)

注意的几点:

  1. Read View视图是在进行SELECT之前创建的,而不是在事务刚begin时创建的。
  2. RR 级别下Read View复制全局Read View;RC 不复制Read View, 而是共享全局Read View.
  3. RR 级别下事务内Read View一旦创建就不变化了。

参考

  1. https://m.imooc.com/article/17290
  2. https://github.com/zhangyachen/zhangyachen.github.io/issues/68

你可能感兴趣的:(我是如何低效的看TiKV代码的(三))