深入理解MVCC与BufferPool缓存机制

一、MVCC多版本并发控制机制

MySql在可重复读隔离级别下如何保证事务较高的隔离性,我们上篇文章中提到过,同样的sql查询语句在一个事务里多次执行查询结果相同,就算其它事务对数据有修改也不会影响当前事务sql语句的查询结果

这个隔离性就是靠MVCC(Multi-Version Concurrency Control)机制来保证的,对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥,而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。

Mysql在读已提交可重复读隔离级别下都实现了MVCC机制。

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

undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链。
深入理解MVCC与BufferPool缓存机制
深入理解MVCC与BufferPool缓存机制

可重复读隔离级别,当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view,该视图在事务结束之前都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成),这个视图由执行查询时所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。

深入理解MVCC与BufferPool缓存机制
read-view:[100, 200], 300

已提交事务:小于数组中最小id的所有事务
未开始事务:大于已创建事务id的所有事务
未提交与已提交事务: 在数组中的事务id, 包含了未提交的事务和已提交的事务。这里面包含的未提交的事务id一定是大于等于200;已提交的事务id可能小于200. 注意,这个数组中的事务id一定是小于300的,但不一定是大于等于100的。比如一个事务id为80的,可能事务提交的比较慢,就有可能落在这个区间中。

版本链比对规则:

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 在视图数组中,表示这个版本是由还没提交的事务生成的,不可见(若 row 的 trx_id 就是当前自己的事务是可见的);
  • b. 若 row 的 trx_id 不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

对于删除的情况可以认为是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版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。

实战分析

深入理解MVCC与BufferPool缓存机制
1、事务id为100的在第3行进行了更新操作,其实才是才会生成事务id,并不是begin或者start transaction的时候就生成事务id的;

update test set c1 = '123' where id =1;

2、第4行的时候,事务id为200的执行更新操作;

update test set c1 = '666' where id =5;

3、第5行的时候,执行更新操作,生成事务id为300的;

update account set name = 'lilei300' where id = 1

4、第6行的时候,事务id300提交了事务。此时的read-view:[100, 200], 300 .

执行到表格第7行的时候,此时的read-view是:[100, 200],300。 read-view是一个数组,由数组里最小的事务id未提交的事务id已创建的最大的事务id组成。这里,100是数组中最小的事务id,100和200是未提交的事务id,此时最大的事务id是300, 由这三个事务id构成了此时的read-view。

深入理解MVCC与BufferPool缓存机制
5、此时的第8行的查询结果是:lilei300. 这个是怎么计算出来的呢?

select name from account where id = 1;   

此时的read-view:[100, 200], 300

根据上面的比对规则,首先会从undo日志的最新记录开始查询,查询当前这个事务到底应该读到哪一条数据记录。此时需要判断是展示lilei还是liei300?此时最新的记录的事务id是300,300不在活跃的视图数组中,根据比较规则,这个版本是已经提交了的事务生成的,可见。

6、继续执行第9和第10行,事务id100进行了两次更新操作;

update account set name = 'lilei1' where id = 1;

update account set name = 'lilei2' where id = 1;

此时的undo日志链为:
深入理解MVCC与BufferPool缓存机制

上一篇:MySQL(七)MVCC【多版本并发控制机制】与BufferPool【缓存机制】


下一篇:前端js使用jszip实现文件压缩功能