文章目录
- 前言
- 什么是MVCC?
- 一、undo日志版本链
- 二、read-view一致性视图
- 2.1、read-view视图的组成
- 2.2、undo版本链比对机制
- 三、模拟MVCC获取值流程
- 3.1、阶段1
- 3.2、阶段2
- 3.3、阶段3
- 总结
前言
最近在梳理自己的知识点,发现MVCC这一块理解不太深刻,去网站上查询了下,也没看到容易理解的,所以有了这篇文章,通过模拟多个事务执行,清晰的可以看见每一步执行步骤,可以清晰的对MVCC有个深刻认识。
什么是MVCC?
Mysql在可重复读这个隔离性就是靠MVCC(Multi-Version Concurrency Control)机制来保证的,对一行数据的读和写两个操作默认是不会通过加锁互斥来保证隔离性,避免了频繁加锁互斥;
而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。
Mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。
一、undo日志版本链
undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个**隐藏字段trx_id(事务ID)和roll_pointer(指针)**把这些undo日志串联起来形成一个历史记录版本链;
简单理解就是undo日志版本链其实就是回滚日志。
下图可以看见有一张account表,每一行字段有id和name,隐藏字段trx_id(事务ID)和roll_pointer(指针,指向修改前的那一条数据);
二、read-view一致性视图
在可重复读隔离级别,当事务开启、执行任何查询sql时会生成当前事务的一致性视图read-view;
该视图在事务结束之前都不会变化(如果是读已提交隔离级别在每次执行查询sql时都会重新生成);
2.1、read-view视图的组成
由执行查询时生成,所有未提交事务id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id)组成,事务里的任何sql查询结果、需要从对应版本链里的最新数据开始逐条跟read-view做比对从而得到最终的快照结果。
2.2、undo版本链比对机制
版本链比对规则:
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获取值流程
3.1、阶段1
如下图transaction100、transaction200都未提交(这2个就是未提交的事务id数组),加上transaction300已经提交的事务(最大事务id)组成;
在#select1执行查询时就会生成read-view视图;
readview包含的实际值:[100,200], 300
这里数组最小的100,也就是min_id,最大的ID,300为max_id;
下图有3个分区,mysql根据版本比对规则去匹配,拿到最终的快照;
执行下面sql,结果返回的是lilei300?为什么?
select name from account where id = 1
拿上面的sql案例解析
这里时候查询到的数据事务id是300,所以满足上面第3种情况,min_id <=trx_id<= max_id,300是最大事务id,他不在未提交的事务数组内,所以3:a不满足,所以他满足了3:b,表示已经提交了事务,是可见的,所以上面的sql查询结果是lilei300。
3.2、阶段2
然后接着上面的案例继续往下执行,这时候Transaction100继续执行2条uapdate,然后select1继续查询同一个sql。
然后拿到了之前的那个一致性视图,然后从版本里最新的一条数据去取,发现现在最新一条数据事务ID为100,然后去比对。
执行顺序
1、他会先找到最后update的那一条数据(lilei2,他的事务id是100,),然后按照规则去比对,事务100他是未提交的事务,所以也是落在黄色区域,然后他满足3:a,因为他在事务数组中,所以他是不可见。
2、然后就会往上找(找到lilei1),然后发现他也是落在黄色区域并且也是在数组内,所以满足3:a所以也是不可见。
3、在往上找(找到lilei300),事务id是300,不在数组内,可见,mvcc到此结束。
3.3、阶段3
这里先把Transaction100事务提交,并且transaction200执行了2条update,最后select1再次进行查询。
然后拿到了之前的那个一致性视图,然后从版本里最新的一条数据去取,发现现在最新一条数据事务ID为200,然后去比对。
执行顺序
其实和上面阶段2一样,因为可重复读的一致性视图,没有变,所以事务200,依然属于未提交数组内,所以往上找,找到事务100,还是不可见,直到找到事务300,可见,结束。
总结
undo日志版本链(回滚日志);
read-view一致性视图,包含未提交事务的id和最大id;
比对规则用于查找可见的数据;
MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据。