MySQL MVCC底层实现逻辑

本文深入解析MySQL中InnoDB存储引擎的MVCC(多版本并发控制)原理,包括隐式字段、事务版本号、undo日志、版本链、ReadView及其匹配规则。通过实例演示MVCC在不同隔离级别下的行为,阐述快照读和当前读的区别,并讨论MVCC如何解决幻读问题。
摘要由CSDN通过智能技术生成

MVCC实现原理是一道非常高频的面试题,底层的原理还是比较难以理解,今天我查看了一些文章,试着做一个简单的梳理,资料全部来自于互联网,我只是做个简单的收录和整理,以便用时翻阅。   --------------------生活纵有千般苦,莫要虚度一世人

  

目录

  

1.先来看看这张图引出MVCC:

2.MVCC中的 概念:

3.MVCC的流程演示:

4.快照读和当前读

5.MVCC是否解决了幻读问题呢?或者在InnoDB下是如何解决幻读问题的?


1.先来看看这张图引出MVCC:

 

 对于图上的数据库事务,事务的特性,事务并发存在的问题,隔离级别这些比较基础的概念就不说了。

直接抛出我们的疑问  数据库是如何保证事务的隔离性的?

 答:数据库是通过加锁,来实现事务的隔离性的。加锁确实好使,可以保证隔离性。比如串行化隔离级别就是加锁实现的。但是频繁的加锁,导致读数据时,没办法修改,修改数据时,没办法读取,大大降低了数据库性能。那么,如何解决加锁后的性能问题的?

答案就是,MVCC多版本并发控制!它实现读取数据不用加锁,可以让读取数据同时修改。修改数据时同时可读取。

关于MySQL锁的文章这里就不展开了,感兴趣的可以看我的另外一篇的博客CSDN

2.MVCC中的 概念:

隐式字段

对于InnoDB存储引擎,在数据库的每一条数据其实都有两个隐藏的字段,事务id(trx_id)和回滚指针(roll_pointer),如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列row_id

在数据库里面大概就是这个样子:

      

  比如我们插入这个条记录的时候,就会把当前这条事务的id插入到这条数据的隐藏字段。

事务版本号

事务每次开启前,都会从数据库获得一个自增长的事务ID,可以从事务ID判断事务的执行先后顺序。这就是事务版本号。这里要注意啊  普通的select语句比如select * from user 这种语句是没有事务ID生成的哈,只有insert,update这些才会为当前的session生成事务Id的.

undo log

undo log是回滚的日志,可以简单的理解为记录了MySQL的一些操作,当发生事务回滚的时候根据这些记录去回滚数据。

  1. 事务回滚时,保证原子性和一致性。

  2. 用于MVCC快照读

版本链

多个事务并行操作某一行数据时,不同事务对该行数据的修改会产生多个版本,然后通过回滚指针(roll_pointer),连成一个链表,这个链表就称为版本链

ReadView 

  它就是事务执行SQL语句时,产生的读视图。实际上在innodb中,每个SQL语句执行前都会得到一个Read View。这里要注意了,在一次的session中,第一次select的时候,就会生成这个一致性的视图,他是由查询的时候所有未提交的事务id的数组,和已创建的(包含已提交和未提交)最大的事务id组成,查询的数据结ReadView 做比对,从而得到快照的结果。隔离级别不一样,ReadView 的工作方式就不一样,可以这样说,在可重复读与读已提交的这两个隔离级别下,他们的实现就是ReadView 的生成不一样。

匹配规则:

3.MVCC的流程演示:

下面我们来讲一个实际的案例,通过实际的案例来套这个MVCC的原理

 

首先我们假设数据库中存在了这样的一条数据 ,

    

假设我开启了第一个session, 并且执行了一条update语句,操作的表不一定是我们的account表,执行update语句是为了生成一个事务的id,假设生成的事务id就是100,接着开启第二个session,执行update语句,假设生成的事务id是200,

紧接着开启第三个session,这个是跟新了数据库的account表,生成的事务id为300,并且执行了提交的操作.  update account  set name='lilei03'  where id=1, 此时数据库的数据会变成这个样子,会插入一条新的记录,同时把之前的那条记录放到undo日志里面去,同时新纪录的回滚指针会指向旧数据

       

 接着execl上的顺序,我再开启一个session,执行一条select语句,select name from account where id=1  凭借我们的经验可知,查询的结果会是lilei03,下面我们在MySQL的默认隔离级别下,也就是可重复读的隔离级别下,用MVCC的角度去看这个select 的执行流程.

     好,一步一步来,首先我们执行select name from account where id=1 ,会为当前的session生成一致性的视图readview,根据定义可知,所有未提交的事务id的数组为[100,200],再加上已创建的(包含已提交和未提交)最大的事务id,为300,所以最终这个readview为[100,200],300

           

 然后他匹配数据是从最新的那一条数据开始匹配,我们最新的一条数据的事务id是300,300落在了黄色的区间,落在黄色的区间后分为了两种情况,此时这个事务id是不在数组[100,200]中的,所以得到的结论是表示这个版本是已提交的事务生成的,所以可见返回。查询的结果为lilei03。

现在我们继续跟着execl里面的命令执行。

 我们在第一个session里面继续执行了两条跟新操作。

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

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

  没有体交,此时版本链变成了这样:

 

这个时候我们再在session4里面执行select语句,select name from account where id=1 ,此时由于这个session中之前执行过一条select语句已经生成了一致性的视图ReadView ,更重要的是由于隔离级别是可重复读,所以此时这个ReadView 不会新计算生成而是直接复制一份最开始生成的,所以此时ReadView 的值依然是[100,200],300。

 根据版本的匹配规则,我们从最新的记录开始找,最新的事务id为100,落在黄色区域中,并且在数据[100,200]中,表示他不可见,继续找,直到找到事务id为300,的才可见,所以此次查询的结果集为name=lilei03,根据经验这个结果也是正确的。

下面跟着execl里面的顺序  我们把session1里面的事务提交,然后再在session2里面执行两条update语句,

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

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

 此时的版本链就变成了这个样子

 我们在session4里面再次执行查询语句   select name from account where id=1,由于隔离级别还是可重复读,所以ReadView 依然是[100,200],300。根据匹配规则,查询的结果集还是name=lilei03

假设这里的事务隔离级别读已提交,那么每次select就会生成新的ReadView ,此时生成的ReadView 就是[200],300,

根据版本的匹配规则,会挑出这一条数据,所以在读已提交的隔离级别下此时查询的结果集为lilei02

 通过上面的例子相信你已经掌握了MVCC的底层机制,下面来总结一下他的大致流程:

我们总结一下大致的流程就是:

  1. 获取事务自己的版本号,即事务ID

  2. 获取Read View

  3. 查询得到的数据,然后Read View中的事务版本号进行比较。

  4. 如果不符合Read View的可见性规则, 即就需要Undo log中历史快照;

  5. 最后返回符合规则的数据

InnoDB 实现MVCC,是通过Read View+ Undo Log 实现的,Undo Log 保存了历史快照,Read View可见性规则帮助判断当前版本的数据是否可见。

不同隔离级别下,Read View的工作方式不同

4.快照读和当前读

  我们读取的Undo Log 里面的数据都叫快照读,那么如何才能当前读最新的呢?

  显式加锁的都是当前读 例如:

 select * from core_user where id > 2 for update;
 select * from account where id>2 lock in share mode;

5.MVCC是否解决了幻读问题呢?或者在InnoDB下是如何解决幻读问题的?

   从理论上讲,RR隔离级别下是存在虚幻读问题的,但是实际测试中呢,MySQL在InnoDB的存储引擎下是没有幻读问题的。

  幻读是指在同一个事物中,前后两次查询相同范围的时候,得到的结果不一致,

 我们要知道在InnoDB中RR隔离级别下已经解决了幻读的问题了。InnoDB里面引入了间隙锁和next-key lock机制去解决幻读问题,

假设现在存在这样的一个B+tree的索引结构,

这个结构里面存在1,4,7,10四个索引元素,

间隙锁是锁定一段范围内的索引记录,其他事物对这个区间的插入跟新删除都会被阻塞。

但是像这种,需要锁定多个索引区间,InnoDB引入了一个叫next-key lock机制,next-key lock相当于间隙锁和记录锁的合集。记录锁锁定存在记录的行也就是行锁,比如for update产生的锁就是行锁,间隙锁锁定的是记录行之间的间隙.

每个数据行上的非唯一索引列都存在一个都会存在一把next-key lock,像这样,当我们事物持有这样一行数据的next-key lock的时候,会锁住一段左开右闭区间的数据。,所以id>4 for update的时候会加一把next-key lock锁,锁定的区间就是(4,7],(7,10],(10,正无穷].

来源:

看一遍就理解:MVCC原理详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时空恋旅人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值