MySQL MVCC多版本并发控制

文章目录

    • 一、MVCC的概念
    • 二、MVCC用于已提交读隔离级别
      • 1. 解决脏读
      • 2. 不能解决不可重复读
      • 3. 不能解决幻读
    • 三、MVCC用于可重复读隔离级别
      • 1. 解决脏读
      • 2. 解决不可重复读问题
      • 3. 不能完全解决幻读

一、MVCC的概念

MVCC是多版本并发控制(Multi-Version Concurrency Control,简称MVCC),是MySQL中基于乐观锁理论实现隔离级别的方式,用于实现已提交读和可重复读隔离级别的实现,也经常称为多版本数据库。MVCC机制会生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定的级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本(系统版本号和事务版本号)。
MVCC多版本并发控制中,读操作可以分为两类:

  1. 快照读(非锁定读):读的是记录的可见版本,不用加锁。如 select做的都是快照读,会把已经commit的数据生成一个快照(这就可以防止不可重复读)
  2. 当前读:读取的是记录的最新版本,并且返回当前读的记录。如 insert,delete,update,select…lock in share mode/for update这些操作,都是读的是最新的数据。
    MVCC:每一行记录实际上有多个版本,每个版本的记录除了数据本身之外,增加了其它字段(DB_ROW_ID、DB_TRX_ID、DB_ROLL_PTR)

已提交读隔离级别,每次select查询时都重新生成快照(Read View)
可重复读隔离级别,事务第一次select查询时,生成一个当前事务全局性的快照(Read View),并且只生成一次快照

快照内容读取原则:

  • 版本未commit,无法读取生成快照
  • 版本已commit,但是在快照创建后提交的,无法读取
  • 版本已commit,但是在快照创建前提交的,可以读取
  • 当前事务内自己的更新,可以读到

二、MVCC用于已提交读隔离级别

1. 解决脏读

设置隔离级别为已提交读并开启事务
在这里插入图片描述
MySQL MVCC多版本并发控制_第1张图片
可以看出已提交读解决了脏读问题。不管是已提交读还是可重复读,只要select,就会产生数据快照,以后查询的时候查询的都是快照上的数据,不会查最新的数据,这也就是非锁定读,效率比较高,不像串行化,都是通过锁来做事务间的并发互斥,而非锁定读都是在快照上进行的操作。

在已提交读隔离级别,每一次select都会产生一个新的数据快照,当事务1进行更改的时候,事务2又去select,重新产生数据快照(有可能和前面的快照相同),然而产生新的数据快照的前提是新的数据已经被事务正确commit,prepare状态的数据不会出现在快照中

数据有2种状态:prepare(未提交时)和commit(已提交)

事务2第二次select的时候,由于事务1并没有commit新的数据(数据处于prepare状态),当又一次产生数据快照时,产生的数据快照还是undo log回滚日志的链表指向的旧数据,这就解决了脏读问题

然而,在已提交读隔离级别依然会发生不可重复读的现象(两次查询,得到的数据内容不一样,属于正确读取的范围)

2. 不能解决不可重复读

已提交读不能防止不可重复读发生,事务2第一次select这条数据的时候,事务1还没有修改,所以MVCC生成的快照读是用原始数据生成的快照读,但是事务1现在把这条数据改了并且正常commit,符合生成数据快照时的基本要求,此时事务2又select这条数据,每一次select都会产生新的快照,这次就是拿新的数据产生快照。
在这里插入图片描述

3. 不能解决幻读

回滚并开启事务
在这里插入图片描述

MySQL MVCC多版本并发控制_第2张图片
发现同样的条件下查询的数据量不一样,出现了幻读,分析:
MySQL MVCC多版本并发控制_第3张图片
此时事务2再去select * from user where age=16;首先生成数据快照,先给数据拍照如下图:
MySQL MVCC多版本并发控制_第4张图片
然后再从里面找出age=16的记录
MySQL MVCC多版本并发控制_第5张图片
为什么一个事务能实时展现出另一个事务增加的满足同样查询条件的已提交的数据,导致我们这个事务出现幻读?

因为每一次select都会重新产生一次新的数据快照,其他事务增加了和当前事务查询条件相同的数据,并且已成功commit提交,导致当前事务再次以同样的条件查询时,数据多了!

三、MVCC用于可重复读隔离级别

1. 解决脏读

prepare状态的数据是不会出现在数据快照上的,对commit已提交的数据生成数据快照,然后在数据快照上查询。和已提交读分析的原因一样。

2. 解决不可重复读问题

可重复读第一次select产生数据快照,而且只产生一次。

设置可重复读隔离级别,回滚并开启两个事务
MySQL MVCC多版本并发控制_第6张图片
事务2第一次select产生了数据快照,因为在可重复读级别下,以后再select都不会产生数据快照
MySQL MVCC多版本并发控制_第7张图片

产生的数据快照如下:

MySQL MVCC多版本并发控制_第8张图片
事务1update,然后commit
MySQL MVCC多版本并发控制_第9张图片
update以后,表格如下所示:
MySQL MVCC多版本并发控制_第10张图片
我们事务2再次select id=12的数据,这时还是在第一次select拍照上查数据,解决了不可重复读。
在这里插入图片描述
如果事务2在事务1更新数据之前没有select过,那么更新之后执行select会产生数据快照,可以查到更新后的数据。
MySQL MVCC多版本并发控制_第11张图片

3. 不能完全解决幻读

先查看表的数据
MySQL MVCC多版本并发控制_第12张图片
回滚并开启事务
在这里插入图片描述
MySQL MVCC多版本并发控制_第13张图片
事务1生成的快照如下:
MySQL MVCC多版本并发控制_第14张图片
事务2第一次select是两条数据,事务1 insert之后,事务2再次select依然是两条,看似解决了幻读,其实只是部分解决(并不能完全解决幻读)

那我们看一下为什么是部分解决幻读

事务1 insert然后commit后,表格的数据应该是这样的
MySQL MVCC多版本并发控制_第15张图片
此时事务2update
MySQL MVCC多版本并发控制_第16张图片

可以看到update找到了id=24的数据,这就证明update做的是当前读(读最新的commit状态的数据),而不是快照读,因为快照上根本没有id=24的数据。
MySQL MVCC多版本并发控制_第17张图片
其实1000是事务1的ID,2000是事务2的ID

当前事务是可以看到自己事务修改、更新的数据的。
当事务2再次select的时候,就可以看到id=24的数据了,这就发生了幻读。
MySQL MVCC多版本并发控制_第18张图片
所以在可重复读级别下,并没有完全解决幻读问题

你可能感兴趣的:(数据库,mysql,数据库)