瞬间理解MVCC多版本并发控制


【MVCC概念】:MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。MVCC在MySQL InnoDB中的实现主要是为了提高数据库的并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,非阻塞并发读。

一、基础知识

1.当前读和快照读

  • 当前读
    读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。如下操作都属于当前读
select .....lock in share mode(共享锁)
select .....for update
update
insert
delete
(排他锁)
  • 快照读
    1.像不加锁的select操作就是快照读。
    2.快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读
    3.之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现就是基于多版本并发控制(MVCC),可以理解MVCC为行锁的一个变种,但在很多的情况下,避免了加锁操作,降低了开销。
    4.既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

说白了,MVCC就是为了实现读和写冲突不加锁,而这个读指的就是快照读,而非当前读,当前读实际上是一种加锁的操作,是悲观锁的体现。

2.当前读、快照读和MVCC的关系

1.MVCC多版本并发控制是维持一个数据的多个版本,使得读写操作没有冲突的概念,只是一个抽象概念。
2.快照读就是MySQL实现MVCC理想模型中的其中一个非阻塞读功能
3.MVCC模型在MySQL中的具体实现原则是由**3个隐式字段、undo日志、ReadView**等去完成的。

3. MVCC能解决什么问题,好处是?

数据库的并发场景有三种,分别为:

  1. 读-读:不存在问题,不需要并发控制;
  2. 读-写:有线程安全问题,可能遇到脏读、不可重复读、幻读;
  3. 写-写:有线程安全问题,可能存在更新丢失问题,比如第一类更新丢失,第二类更新丢失;

MVCC带来的好处是?
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制。
为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读事务开始前的数据库的快照。所以MVCC可为数据库解决一下问题:
1、 在并发读写数据库时,可做到在读操作时不用阻塞写操作,写操作时也不用阻塞读操作,提高了数据库并发读写性能;
2、同时解决了脏读、不可重复度等事务隔离问题(解决部分读写问题),但不能解决更新丢失的问题(写写冲突解决不了)。

【总结】
为了不只让数据库采用悲观锁这样性能不佳的形式去解决并发问题,因此使用MVCC这样的非阻塞方式来解决问题,在数据库中,因为有了MVCC,所以我们可以形成两个组合:
1、MVCC+悲观锁
MVCC解决度读写冲突,悲观锁解决写写冲突;
2、MVCC+乐观锁

这种组合的方式,在最大程度上提升DB的并发性能,并解决读写冲突,和写写冲突导致的问题。

二、MVCC实现原理

其主要实现原理是依赖记录中的==3个隐式字段undo日志、ReadView读视图==来实现,我们这三点来深入了解:

1、隐式字段

数据库表中的每行记录除了我们自定义字段外,还有数据库隐式定义的:
DB_TRX_ID:创建或者最后一次修改记录的事务ID

DB_ROW_ID:隐藏主键

DB_ROW_PTR:回滚指针(指向上一个历史记录) ----- 与undolog配合使用
例如下图所示:
image-20220601110941850

2、undo log 回滚日志

主要分为两种:

  • insert undo log
    代表事务在insert新记录时产生的undo log,只在事务回滚时需要,并且在事务提交后可以被立即丢弃;

  • update undo log
    事务在进行update或dalete时产生的undo log;不仅在事务回滚时需要,在快照读时也需要,所以并不能随便删除。
    对MVCC有帮助的实际是update undo log,undo log实际上就是保存数据的历史版本状态。
    当不同的事务对同一条记录做修改时,会导致该记录的undo log形成一个链表,链表的首链是最新的历史记录,而链尾是最早的历史记录。

举个例子
1、比如事务1插入person表一条新记录,记录如下:
image-20220601111303280
2、现在来了个事务2对该记录的name做出了修改,改为lisi
image-20220601111327848

  1. 在事务2修改该行数据时,数据库会先对该行进行加排它锁
  2. 然后把该数据拷贝到undo log中, 作为旧记录,即在undo log中有当前行的拷贝副本;
  3. 拷贝完毕后,name被改成了lisi; 隐藏字段:DB_TRX_ID:2 、DB_ROLL_PTR:指向uodolog地址的指针(历史记录)
  4. 事务提交后,释放锁;

image-20220601111727158

3、事务3将age改为21

  1. 在事务3修改行数据时,数据库也先为该行加锁;
  2. 然后将该行数据拷贝进undo log中,作为旧记录;将最新的旧数据作为链表头,插在该行记录的undo log最前面
  3. 修改该行age为21,DB_TRX_ID:3;DB_ROLL_PTR:为刚拷贝到undo log的副本记录;
  4. 提交事务,释放锁
    image-20220601111927098

3、ReadView 读视图

事务在进行快照读时,产生的读视图。在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,此快照记录并维护当前活跃事务的ID(当每个事务开启时,都会被分配一个ID,这个ID是递增的,所以最新的事务,ID值越大)读视图中的字段:

  • trx_list:系统活跃事务
  • up_limit_id:活跃列表中最小的事务ID
  • low_limit_id:尚未分配的下一个事务ID

所以,我们知道Read View主要是用来做可见性判断的,即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,即可能是当前的最新数据,也可能是该行记录的undo log里面的某个版本的数据。

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(最后一次修改数据的事务ID)取出,与系统当前其他活跃事务的ID做对比(ReadView维护)。判断此DB_TRX_ID所在的旧记录当前事务是否能看见。
判断条件如下:
image-20220601153635193
举个例子理解
MVCC的整体流程是怎么样的呢?我们来模拟理解一下:
1、事务0完成了数据的插入后,事务2对该数据执行了快照读,此时数据库为该行数据生成了一个ReadView读视图。此时还有事务1和事务3在活跃中,事务4在事务2快照读前一刻提交更新了,所以Read View记录了系统当前活跃事务1、3的ID,维护在一个列表trx_list
image-20220601144743970
2、此时Read View中维护的数据为:
trx_list:1、2、3
up_limit_id:1
low_limit_id:5

3、事务4在事务2执行快照读前,提交了事务,所以我们的事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID去跟up_limit_id,low_limit_id,trx_list进行比较,判断当前事务2能看到的记录版本是哪个
在这里插入图片描述

4、所以
1)先拿该记录DB_TRX_ID字段记录的事务ID:4去跟Read View的up_limit_id比较,看4是否小于up_limit_id:1;
2)如果不符合条件,继续判断4是否大于等于low_limit_id:5,也不符合条件;
3)最后判断4是否处于trx_list中的活跃事务,最后发现事务ID为4的事务不在当前活跃事务列表中,符合可见性条件------>因此事务2能读到的最新数据记录是事务4所提交的版本

但如果是事务2在事务4提交之前,快照读过一次,则在事务4再提交一遍后,事务2快照读后是读不到事务4修改的最新版本的。
因为:
事务2第二次快照读的ReadView是沿用最初的
image-20220601155954396

Db_TRX_ID是在活跃事务列表中,认为是还没有commit,所以经判断后,当前事务是看不到的。

这也正是Read View生成时机的不同,从而造成RC、RR级别下快照读的结果不同。
image-20220601154158760

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值