Mysql的MVCC机制与BufferPool缓存机制

353 阅读5分钟

写在前面

前面上一篇Mysql事务隔离级别和锁机制,可以看到MySQL在可重复读隔离级别下,可以保证事务较高的隔离性。这个隔离性是通过MVCC(Multi-Version Concurrency Control)机制来保证的,对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥。

undo日志版本链与read view机制详解

undo日志版本链是指一行数据被多个事务依次修改后,在每个事务修改后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer,把这些undo日志串联起来形成一个历史记录版本链。
如图所示: image.png可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在第一次select的时候生成,在事务结束之前都不会变化如果是读已提交隔离级别在每次执行查询(select)sql时都会重新生成,read-veiw是由执行查询时所有未提交事务id数组里最小的id未min_id已创建的最大事务id(max_id)组成,事务里的任何sql查询结果都需要从对应版本链(上图)的最新数据开始逐一跟read-view做对比,从而得到最终的快照结果。
版本链比对规则
1.如果row的trx_id落在绿色区域(trx_id<min_id),表示这个版本是已提交的事务生成的,这个数据是可见的;
2.如果row的trx_id落在红色区域(trx_id>max_id),表示这个版本还没启动,是不可见的(若row的trx_id就是当前自己的事务是可见的);
3.如果row的trx_id落在黄色部分(min_id<=trx_id<=max_id),那就包含两种情况:
a.若row的trx_id在视图数组(read-veiw)中,表示这个版本是由还没提交的事务生成的,不可见。
b.若row的trx_id不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。
举例:
image.png 分析:
如图所示在不同时刻(1-13)进行事务80(trx_id 80)、事务100(trx_id 100)、事务200(trx_id 200)、事务300(trx_id 300)、查询1、查询2操作。trx_id 80可以认为是已经提交的事务。
时刻1:开启事务begin;
时刻2:trx_id 80提交事务,trx_id 100进行了一次update操作,事务未提交;
时刻3:trx_id 200进行了一次update操作,事务未提交;
时刻4、5:trx_id 300进行了一次update操作,并且提交了事务;
时刻6:select 1 进行了一次查询操作,此时会生成read-view,根据生成原则由最小未提交事务id(trx_id 100)和最大已创建事务id(trx_id 300)组成,即read-view:([100,200],300),根据版本链比对规则:此时的记录版本链条如下图:
image.png
此时的trx_id 为300,到read-view([100,200],300)中去比较发现不在[100,200]中,说明对查询select 1 可见。查询的结果为name300。
时刻7、8:trx_id 100进行了两次的update操作,但事务还未提交。此时的版本链条如下图:
image.png
时刻9:select 1进行了一次查询操作,在可重复读隔离级别下,因为在时刻6的时候,已经进行了一次查询操作,这时候的read-view还是([100,200],300),根据版本链比对规则:在最新的记录版本链条,从上往下找,先是trx_id 100,name是name2的记录,到read-view对比,发现在[100,200]中,对当前事务select 1不可见,继续往下找trx_id 100,name是name1的记录,同理得,对当前事务不可见,接着向下查找到,trx_id 300,name是300的记录,对比发现300不在[100,200]中,即对当前事务可见,于是查询结果为name300。
时刻10、11:trx_id 100提交了事务,trx_id 200进行了二次update操作,但事务还未提交。此时的版本链记录如下:
image.png
时刻12:select 1 进行了一次查询操作,同理可知select 1在之前已经进行过查询操作,因此,此时的read-view还是([100,200],300),分析步骤如时刻9所示,不做赘述。select 2进行了一次查询操作,因为select 2 是第一次查询,因此根据规则会生成read-view([200],300),根据版本链比对规则:从上往下查找trx_id 200,name为name4在[200]中,对当前事务不可见,同理的name为name3也对当前事务不可见,当到trx_id 100,name为name1时,发现trx_id 100 < trx_id 200,说明这个版本是已经提交的事务生成的,对当前事务可见。即查询结果为name2。
时刻13:trx_id 200 进行了commit操作。此时版本链条为本篇第一张图所示。

小结

对于删除的情况可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将trx_id修改成删除操作的 trx_id,同时在该条记录的头信息(record header)里的(deleted_flag)标记位写上true,来表示当前记录已经被 删除,在查询时按照上面的规则查到对应的记录如果delete_flag标记位为true,意味着记录已被删除,则不返回数据。
注意:begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句, 事务才真正启动,才会向mysql申请事务id,mysql内部是严格按照事务的启动顺序来分配事务id的。
MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取 同一条数据在版本链上的不同版本数据。

Innodb引擎BufferPool缓存机制

原理图:
MySQL buffer pool.png
MySQL中的bufferpool流程图,大致如上图所示。具体细节没有很详细的说明,待后续对这一块的内容更加深入的学习了解后再来补充。