1、什么是MVCC?

MVCC(Multi-Version Concurrency Control | 多版本并发控制)

MVCC通过保存数据的历史版本,根据比较版本号来选择要显示的数据,从而达到读取数据时不需要加锁就可以保证事务的隔离性。

2、Innodb MVCC实现的核心知识点


1、事务版本号
2、表的隐藏列。
3、undo log
4、 read view

2-1、事务版本号

每次事务开启前都会从数据库获得一个自增长的事务ID,可以从事务ID判断事务的执行先后顺序。

2-2、表格的隐藏列

Innodb存储引擎给每个数据表都加了三个隐藏字段:DB_TRX_ID、DB_ROLL_PTR、DB_ROW_ID。
DB_TRX_ID: 记录操作该数据事务的事务ID;
DB_ROLL_PTR:回滚指针,指向上一个版本数据在undo log 里位置的指针;
DB_ROW_ID: 隐藏ID ,当数据表没有指定主键时,数据库会自动以这个列来作为主键,生成聚集索引;

2-3、Undo log

Undo log 主要用于记录数据被修改之前的日志,在表信息修改之前先会把数据拷贝到undo log 里,当事务进行回滚时可以通过undo log 里的日志进行数据还原。
Undo log 的用途
(1)保证事务进行rollback时的原子性和一致性,当事务进行回滚的时候可以用undo log的数据进行恢复。
(2)用于MVCC快照读的数据,在MVCC多版本控制中,通过读取undo log的历史版本数据可以实现不同事务版本号都拥有自己独立的快照数据版本。

2-4、事务版本号、表格的隐藏列、undo log的关系

我们通过一次数据修改的过程来了解事务版本号、表格隐藏的列和undo log之间的关系。
(1)首先准备一张原始原始数据表
image.png

(2)开启一个事务A: 对user_info表执行 update user_info set name =“李四”where id=1 会进行如下操作
1、首先获得一个事务编号 104
2、把user_info表修改前的数据拷贝到undo log
3、修改user_info表 id=1的数据
4、把修改后的数据事务版本号改成当前事务版本号,并把DB_ROLL_PTR 地址指向undo log数据地址。

(3)最后执行完结果如图:
image.png

2-5、Read view

在innodb 中每个SQL语句执行前都会得到一个read view。

Read view 的几个重要属性

(1)trx_ids:mysql中未提交的事务id集合;
(2)low_limit_id:集合中最大的事务id;
(3)up_limit_id,集合中最小的事务id;
(4)creator_trx_id:当前执行的事务id;

Read view 匹配条件

(1)数据事务ID小于up_limit_id则显示
如果数据事务ID小于活跃事务的最小ID,则说明该数据是在所有活跃事务开启之前就已经存在的,可以显示。
(2)数据事务ID大于low_limit_id则不显示
如果数据事务ID大于活跃事务的最大ID,则说明该数据是在所有活跃事务开始之后才创建的,所以数据不予显示。
(3)数据事务ID 大于up_limit_id 并且小于low_limit_id
用数据事务ID 与trx_ids 集合中的事务ID进行匹配,如果当前事务ID不存在于活跃事务ID集合(说明事务已经commit了),或者数据事务ID等于creator_trx_id(这说明该数据就是当前事务修改的),满足上面的条件则可以显示。
(4)不满足read view条件时候,从undo log里面获取
当数据的事务ID不满足read view条件时候,从undo log里面获取数据的历史版本,然后数据历史版本事务号回头再来和read view 条件匹配 ,直到找到一条满足条件的历史数据,或者找不到则返回空结果;

3、Innodb实现MCC的原理

image.png

3-1、模拟MVCC实现流程

下面我们通过开启两个同时进行的事务来模拟MVCC的工作流程。

(1)创建user_info表,插入一条初始化数据
image.png

(2)事务A和事务B同时对user_info进行修改和查询操作
事务A:update user_info set name =”李四”
事务B:select * fom user_info where id=1

问题:
先开启事务A ,在事务A修改数据后但未进行commit,此时执行事B。最后返回结果如何。

执行流程如下图:
image.png

执行流程说明:
(1)事务A:开启事务,首先得到一个事务编号102;
(2)事务B:开启事务,得到事务编号103;
(3)事务A:进行修改操作,首先把原数据拷贝到undolog,然后对数据进行修改,标记事务编号和上一个数据版本在undo log的地址。
image.png

(4)事务B: 此时事务B获得一个read view ,read view对应的值如下
image.png

(5)事务B: 执行查询语句,此时得到的是事务A修改后的数据
image.png

(6)事务B: 把数据与read view进行匹配,
数据事务ID为102 大于up_limit_id
数据事务ID为102 小于low_limit_id
数据事务ID为102存在于 trx_ids,
数据事务ID为102不等于creator_trx_id
发现不满足read view显示条件,所以从undo lo获取历史版本的数据再和read view进行匹配,最后返回数据如下。
image.png


4、补充

各种事务隔离级别下的Read view 工作方式

  1. READ_UNCOMMITTED 级别的事务不会获取read view 副本。

  2. RC(read commit) 级别下同一个事务里面的每一次查询都会获得一个新的read view副本。这样就可能造成同一个事务里前后读取数据可能不一致的问题(幻读)

image.png

  1. RR(重复读)级别下的一个事务里只会获取一次read view副本,从而保证每次查询的数据都是一样的。

image.png

快照读和当前读

快照读
快照读是指读取数据时不是读取最新版本的数据,而是基于历史版本读取的一个快照信息(mysql读取undo log历史版本) ,快照读可以使普通的SELECT 读取数据时不用对表数据进行加锁,从而解决了因为对数据库表的加锁而导致的两个如下问题
1、解决了因加锁导致的修改数据时无法对数据读取问题;
2、解决了因加锁导致读取数据时无法对数据进行修改的问题;

当前读
当前读是读取的数据库最新的数据,当前读和快照读不同,因为要读取最新的数据而且要保证事务的隔离性,所以当前读是需要对数据进行加锁的
Update 、delete、 insert、 select ….lock in share mode、 select for update 是基于当前读。