MVCC多版本控制

多版本控制(Multiversion Concurrency Control): 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。在内部实现中,InnoDB通过undo log保存每条数据的多个版本,并且能够找回数据历史版本提供给用户读,每个事务读到的数据版本可能是不一样的。在同一个事务中,用户只能看到该事务创建快照之前已经提交的修改和该事务本身做的修改。

MVCC只在 Read Committed 和 Repeatable Read两个隔离级别下工作。其他两个隔离级别和MVCC不兼容,Read Uncommitted总是读取最新的记录行,不需要MVCC的支持;Serializable 则会对所有读取的记录行都加锁,单靠MVCC无法完成。

MySQL的InnoDB存储引擎默认事务隔离级别是RR(可重复读),是通过 "行级锁+MVCC"一起实现的,正常读的时候不加锁,写的时候加锁。而 MCVV 的实现依赖:隐藏字段、Read View、Undo log。

MVCC(Multi-Version Concurrent Control),基于快照隔离机制(Snapshot Isolations)进行多版本并发控制,是一种以乐观锁为理论基础的,用来解决读-写冲突的无锁并发控制。

 

MVCC的实现依赖于 每行的隐藏字段,DB_TRX_ID,DB_ROLL_PTR,删除标记位,还有read_view。
3.1 三个隐藏字段

在这里插入图片描述

  • DB_TRX_ID: 事务id,存的是创建事务的id,或者最后一次更新事务的id;
  • DB_ROW_ID: 主键id,建表时如果没设置主键,则此字段会成为主键发挥作用;
  • DB_ROLL_PTR: 回滚指针,指向上一个版本的数据,如果没有上个版本,则为null。

 

其中,DB_ROLL_PTR结合undo log实现。

4.2 undo log
undo log称为回滚日志,是InnoDB MVCC事务特性的重要组成部分,存在形式就是一种日志文件。

undo log的数据结构,非常复杂,可以简单理解为链表,链表头部存储最新版本,尾部存储最早版本。通过遍历链表,就可以找到对应版本的数据。

大多数对数据的变更操作包括INSERT/DELETE/UPDATE,其中,

INSERT操作使用insert_undo,因为新插入就只有一个版本,因此产生的Undo日志可以在事务提交后直接删除;
而对于UPDATE/DELETE则需要维护多版本信息,在InnoDB里,UPDATE和DELETE操作产生的Undo日志被归成一类,即update_undo;mvcc使用的undolog,就是update_undo。
只有undolog,还不足以实现mvcc,因为既然undolog维护了那么多版本,遍历的时候,应该去找哪一个版本呢?这里必然牵扯到一种规则,这种规则在不同的数据库隔离级别下是不一样的。

innodb具体实现的时候,使用了一种叫做readview的定西。

4.2 readview
readview称为读视图,是事务在进行快照读的时候产生的,算是一种数据结构。

readview中包含3个参数:

trx_list :系统活跃的(事务在执行,还没有提交)事务id列表;
up_limit_id :trx_list列表中最小的事务id;
low_limit_id :ReadView生成时刻系统尚未分配的下一个事务ID,也就是目前已出现过的事务ID的最大值+1;比如当前事务id已经分配到了4,那么low_limit_id的值就是5;
每个事务在进行自己的快照读时,都会产生自己事务对应的readview。

例如下图中:

有4个事务同时在执行,事务id分别为1,2,3,4;其中,1,2,3还没有提交,4已经提交,那么当事务2在进行快照读时,产生一个readview,其trx_list存的就是活跃的1,2,3,如下图:

此时,最小的事务id是1,最大的事务id是5 (尚未分配的下一个id),当前最新的事务id是4 (事务4的id)。

可见性算法
上图中右侧黄色部分就是本次查询到底该取哪个版本数据的规则,也叫可见性算法;

查找过程:
遍历undolog的最新数据到最老数据,逐个判断每条log的事务id,当作DB_TRX_ID,然后使用上图中黄色可见性算法比对,最终确定当前遍历的数据是不是目标数据。

读视图的产生时机
在不同的隔离级别下,readview产生的时机是不同的:

RC :每次进行快照读的时候,都会产生新的readview;
RR :只有在第一次进行快照读的时候,才会产生readview,之后的读操作都会使用第一次生成的readview。如果此事务中间发生了update等当前读操作,也会生成新的readview。

————————————————
版权声明:本文为CSDN博主「雨剑yyy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/csdn_20150804/article/details/119766846

posted @ 2022-03-13 14:45  爵士灬  阅读(133)  评论(0编辑  收藏  举报