当前位置: 代码网 > it编程>数据库>Mysql > MySQL中的MVCC底层原理解读

MySQL中的MVCC底层原理解读

2025年02月09日 Mysql 我要评论
简介mvcc(multi-version concurrency control)多版本并发控制,是用来在数据库中控制并发的方法,实现对数据库的并发访问用的。在mysql中,mvcc只在读取已提交(r

简介

mvcc(multi-version concurrency control)多版本并发控制,是用来在数据库中控制并发的方法,实现对数据库的并发访问用的。

在mysql中,mvcc只在读取已提交(read committed)可重复读(repeatable read)两个事务级别下有效。其是通过undo日志中的版本链readview一致性视图来实现的。

mvcc就是在多个事务同时存在时,select语句找寻到具体是版本链上的哪个版本,然后在找到的版本上返回其中所记录的数据的过程。

首先需要知道的是,在mysql中,会默认为我们的表后面添加三个隐藏字段:

  • db_row_id:行id,mysql的b+树索引特性要求每个表必须要有一个主键。如果没有设置的话,会自动寻找第一个不包含null的唯一索引列作为主键。如果还是找不到,就会在这个db_row_id上自动生成一个唯一值,以此来当作主键(该列和mvcc的关系不大);
  • db_trx_id:事务id,记录的是当前事务在做insert或update语句操作时的事务id(delete语句被当做是update语句的特殊情况,后面会进行说明);
  • db_roll_ptr:回滚指针,通过它可以将不同的版本串联起来,形成版本链。相当于链表的next指针。

(注意,添加的隐藏字段并不是很多人认为的创建时间和删除时间,同时在mysql中mvcc的实现也不是通过什么快照来实现的。之所以有这种说法可能是源自于《高性能mysql》一书中对mysql中mvcc的错误结论,然后就人云亦云传开了(注意,我这里一直强调的是mysql中mvcc的实现,是因为在不同的数据库中可能会有不同的实现)。所以说看源码和看官方文档才是最权威的解释)

readview

readview一致性视图主要是由两部分组成:所有未提交事务的id数组已经创建的最大事务id组成(实际上readview还有其他的字段,但不影响这里对mvcc的讲解)。

比如:[100,200],300。事务100和200是当前未提交的事务,而事务300是当前创建的最大事务(已经提交了)。

当执行select语句的时候会创建readview,但是在读取已提交和可重复读两个事务级别下,生成readview的策略是不一样的:读取已提交级别是每执行一次select语句就会重新生成一份readview,而可重复读级别是只会在第一次select语句执行的时候会生成一份,后续的select语句会沿用之前生成的readview(即使后面有更新语句的话,也会继续沿用)。

版本链

所有版本的数据都只会存一份,然后通过回滚指针连接起来,之后就是通过一定的规则找到具体是哪个版本上的数据就行了。

假设现在有一张account表,其中有id和name两个字段,那么版本链的示意图如下:

而具体版本链的比对规则如下,首先从版本链中拿出最上面第一个版本的事务id开始逐个往下进行比对:

(其中min_id指向readview中未提交事务数组中的最小事务id,而max_id指向readview中的已经创建的最大事务id)

如果落在绿色区间(db_trx_id < min_id):这个版本比min_id还小(事务id是从小往大顺序生成的),说明这个版本在select之前就已经提交了,所以这个数据是可见的。或者(这里是短路或,前面条件不满足才会判断后面这个条件)这个版本的事务本身就是当前select语句所在事务的话,也是一样可见的;

如果落在红色区间(db_trx_id > max_id):表示这个版本是由将来启动的事务来生成的,当前还未开始,那么是不可见的;

如果落在黄色区间(min_id <= db_trx_id <= max_id):这个时候就需要再判断两种情况:

  • 如果这个版本的事务id在readview的未提交事务数组中,表示这个版本是由还未提交的事务生成的,那么就是不可见的;
  • 如果这个版本的事务id不在readview的未提交事务数组中,表示这个版本是已经提交了的事务生成的,那么是可见的。

如果在上述的判断中发现当前版本是不可见的,那么就继续从版本链中通过回滚指针拿取下一个版本来进行上述的判断。

演示过程

下面通过一个示例来具体演示mvcc的执行过程(假设是在可重复读事务级别下),当前account表中已经有了一条初始数据(id=1,name=monkey):

transaction 100transaction 200transaction 300无事务id无事务id
1begin;begin;begin;begin;begin;
2update test set a='1' where id = 1;
3update test set a='2' where id = 2;
4update account set name = 'monkey301' where id = 1;
5commit;
6select name from account where id = 1;
7update account set name = 'monkey101' where id = 1;
8update account set name = 'monkey102' where id = 1;
9select name from account where id = 1;
10commit;update account set name = 'monkey201' where id = 1;
11update account set name = 'monkey202' where id = 1;
12select name from account where id = 1;select name from account where id = 1;
13commit;

从左往右分别是五个事务,从上到下是时刻点。其中在第2和3时刻点中事务100和事务200(这里两个事务之间相差100只是为了更加方便去看,正常来说下个事务的id是以+1的方式来创建的)分别执行了一条update语句,这两条语句并无实际作用,只是为了生成事务id的,所以在下面的mvcc执行过程中就不分析这两条语句所带来的影响了,我们只研究account表。而其中最后两个事务,我是注明没有事务id的。因为事务id是执行一条更新操作(增删改)的语句后才会生成(这也是事务100和事务200要先执行一条更新语句的意义),并不是开启事务的时候就会生成。最后两个事务中可以看到就是执行了一些select语句而已,所以它们并没有事务id。

首先来看一下初始状态时的版本链和readview(readview此时还未生成):

其中事务1在account表中创建了一条初始数据。

  • 之后在第1时刻点,五个事务分别开启了事务(如上所说,这个时候还没有生成事务id)。
  • 在第2时刻点,第一个事务执行了一条update语句,生成了事务id为100。
  • 在第3时刻点,第二个事务执行了一条update语句,生成了事务id为200。
  • 在第4时刻点,第三个事务执行了一条update语句,将account表中id为1的name改为了monkey301。同时生成了事务id为300。
  • 在第5时刻点,事务300也就是上面的事务执行了commit操作。
  • 在第6时刻点,第四个事务执行了一条select语句,想要查询一下当前id为1的数据(如上所说,该事务没有生成事务id)。

此时的版本链和readview如下:

因为在第5时刻点,事务300已经commit了,所以readview的未提交事务数组中不包含它。此时根据上面所说的比对规则,拿版本链中的第一个版本的事务id为300进行比对,首先当前这条select语句没有在事务300中进行查询,然后发现是落在黄色区间,而且事务300也没有在readview的未提交事务数组中,所以是可见的。即此时在第6时刻点,第四个事务所查找到的结果是monkey301。

  • 在第7时刻点,事务100执行了一条update语句,将account表中id为1的name改为了monkey101。
  • 在第8时刻点,事务100又执行了一条update语句,将account表中id为1的name改为了monkey102。
  • 在第9时刻点,第四个事务执行了一条select语句,想要查询一下当前id为1的数据。

此时的版本链和readview如下:

注意,因为当前是在可重复读的事务级别下,所以此时的readview沿用了在第6时刻点生成的readview(如果是在读取已提交的事务级别下,此时就会重新生成一份readview了)。然后根据上面所说的比对规则,拿版本链中的第一个版本的事务id为100进行比对,首先当前这条select语句没有在事务100中进行查询,然后发现是落在黄色区间,而且事务100是在readview的未提交事务数组中,所以是不可见的。此时通过回滚指针拿取下一个版本,发现事务id仍然为100,经过分析后还是不可见的。此时又拿取下一个版本:事务id为300进行比对,首先当前这条select语句没有在事务300中进行查询,然后发现是落在黄色区间,但是事务300没有在readview的未提交事务数组中,所以是可见的。即此时在第9时刻点,第四个事务所查找到的结果仍然是monkey301(这也就是可重复读的含义)。

  • 在第10时刻点,事务100commit提交事务了。同时事务200执行了一条update语句,将account表中id为1的name改为了monkey201。
  • 在第11时刻点,事务200又执行了一条update语句,将account表中id为1的name改为了monkey202。
  • 在第12时刻点,第四个事务执行了一条select语句,想要查询一下当前id为1的数据。

此时的版本链和readview如下:

跟第9时刻点一样,在可重复读的事务级别下,readview沿用了在第6时刻点生成的readview。然后根据上面所说的比对规则,拿版本链中的第一个版本的事务id为200进行比对,首先当前这条select语句没有在事务200中进行查询,然后发现是落在黄色区间,而且事务200是在readview的未提交事务数组中,所以是不可见的。此时通过回滚指针拿取下一个版本,发现事务id仍然为200,经过分析后还是不可见的。此时又拿取下一个版本:事务id为100进行比对,首先当前这条select语句没有在事务100中进行查询,然后发现是落在黄色区间内,同时在readview的未提交数组中,所以依然是不可见的。此时又拿取下一个版本,发现事务id仍然为100,经过分析后还是不可见的。此时再拿取下一个版本:事务id为300进行比对,首先当前这条select语句没有在事务300中进行查询,然后发现是落在黄色区间,但是事务300没有在readview的未提交事务数组中,所以是可见的。即此时在第12时刻点,第四个事务所查找到的结果仍然是monkey301。

同时在第12时刻点,第五个事务执行了一条select语句,想要查询一下当前id为1的数据。

此时的版本链和readview如下:

注意,此时第五个事务因为是该事务内的第一条select语句,所以会重新生成在当前情况下的readview,即上图中所示的内容。可以看到,和第四个事务生成的readview并不一样,因为在之前的第10时刻点,事务100已经提交事务了。然后根据上面所说的比对规则,拿版本链中的第一个版本的事务id为200进行比对,首先当前这条select语句没有在事务200中进行查询,然后发现是落在黄色区间,而且事务200是在readview的未提交事务数组中,所以是不可见的。此时通过回滚指针拿取下一个版本,发现事务id仍然为200,经过分析后还是不可见的。此时又拿取下一个版本:事务id为100进行比对,发现是在绿色区间,所以是可见的。即此时在第12时刻点,第五个事务所查找到的结果是monkey102(可以看到,即使是同一条select语句,在不同的事务中,查询出来的结果也可能是不同的,究其原因就是因为readview的不同)。

  • 在第13时刻点,事务200执行了commit操作,整段分析过程结束。

以上演示的就是mvcc的具体执行过程,在多个事务下,版本链和readview是如何配合进行查找的。上面还遗漏了一种情况没有进行说明,就是如果是delete语句的话,也会在版本链上将最新的数据插入一份,然后将事务id赋值为当前进行删除操作的事务id。但是同时会在该条记录的信息头(record header)里面的deleted_flag标记位置为true,以此来表示当前记录已经被删除。所以如果经过版本比对后发现找到的版本上的deleted_flag标记位为true的话,那么也不会返回,而是继续寻找下一个。

另外,如果当前事务执行rollback回滚的话,会把版本链中属于该事务的所有版本都删除掉。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。

(0)

相关文章:

  • docker拉取MySQL8.0镜像以及安装的完整步骤

    一、docker安装mysql镜像搜索imagesdocker search mysql 我配置了阿里云镜像加速器,找不到mysql镜像.由于不知道名原因很多国内拉取镜像都下架了d…

    2025年02月08日 数据库
  • MySQL之锁类型解读

    mysql锁类型解读锁的分类图,如下:锁操作类型划分读锁 : 也称为共享锁 、英文用s表示。针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻塞的。写锁 : 也称…

    2025年02月08日 数据库
  • MySQL分批插入/更新数据方式

    MySQL分批插入/更新数据方式

    mysql分批插入/更新数据在我们的日常开发中,经常会使用到批量insert/update的语句来实现相关的业务功能。而如果数据量比较大的话,会导致sql语句更... [阅读全文]
  • 生产环境的MySQL事务隔离级别方式

    生产环境的MySQL事务隔离级别方式

    生产环境的mysql事务隔离级别mysql 数据库的默认隔离级别是 rr( 可重复读 ),但是很多大公司把隔离级别改成了 rc(读已提交),主要原因是为了提高并... [阅读全文]
  • MySql9.1.0安装详细教程(最新推荐)

    mysql介绍:‌mysql是一个流行的关系型数据库管理系统,由瑞典mysql ab公司开发,目前属于oracle旗下产品。‌ mysql以其体积小、速度快、总…

    2025年02月08日 数据库
  • Mysql分库分表实现方式

    Mysql分库分表实现方式

    1、背景mysql作为做流行的开源数据库,在各大互联网公司被广泛使用。通常我们用一个库就可以满足需求,但是随着业务的增长,数据量和并发量迅速膨胀。mysql单表... [阅读全文]

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com