MySQL ---- MVCC

Posted TheWhc

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL ---- MVCC相关的知识,希望对你有一定的参考价值。

mysql中多个事务并发执行的隔离性是靠MySQL的锁机制实现的,但是写锁和读锁是冲突的,为了提交并发度,所以MySQL通过MVCC(多版本并发控制)方式来解决读写冲突,提高数据库高并发场景下的吞吐性能,也就是说读写、写读这两个操作可以并行,提高了InnoDB的并发度。

在内部实现中,InnoDB通过undo log保存每条数据的多个版本,并且能够找回数据的历史版本提供给用户读,每个事务读到的数据版本可能是不一样的。

基于undo log多版本链以及ReadView机制实现的多事务并发执行的RC隔离级别、RR隔离级别,就是数据库的MVCC多版本并发控制机制

一、前置知识

1、undo log版本链

对于使用InnoDB存储引擎的表来说,它的聚餐索引记录中都有两个隐藏列字段,一个是trx_id,一个是roll_pointer

  • trx_id:最近一次更新这条数据的事务id(更改可以是INSERTDELETEUPDATE操作)
  • roll_pointer:指向了更新事务之前生成的undo log

比如说现在的表有一条记录如下:

image-20210610161855046

对应那条记录的事务id为80,此时记录如下:

image-20210610161918609

(此时insert undo内容是空的)

实际上insert undo只在事务回滚时起作用,当事务提交后,该类型的undo日志就没用了,它占用的Undo Log Segment也会被系统回收

假设之后有两个事务id分别为100、200的事务对这条记录进行UPDATE操作,操作流程如下:

image-20210610162409468 image-20210610162431103

版本链的头节点是当前记录最新的值

2、Read View

判断一下版本链的哪个版本是当前事务可见的

在执行一个事务的时候,会生成一个ReadView,有4个主要内容:

  • m_ids:此时有哪些在MySQL里执行还没提交的
  • min_trx_id:m_ids里最小的值
  • max_trx_id:mysql下一个要生成的事务id,即最大事务id
  • creator_trx_id:这个事务id

有了这个READVIEW,就可以在访问某条记录时,按照如下的规则进行判断就可以确定版本链中哪个版本对当前读事务是否可见:

  1. 如果被访问版本的 trx_id 属性值与 ReadView 中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  2. 如果被访问版本的 trx_id 属性值小于 ReadView 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
  3. 如果被访问版本的 trx_id 属性值大于 ReadView 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
  4. 如果被访问版本的 trx_id 属性值在 ReadView 的 min_trx_id 和 max_trx_id 之间,那就需要判断一下
    trx_id 属性值是不是在 m_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该
    版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

二、RC基于ReadView的实现

RC隔离级别:事务运行期间,只要别的事务修改了数据且提交了,那么你是可以读取到到别人修复的数据的,但是会带来不可重复读问题,以及幻读问题。(RC只是解决了脏读问题)

事务处于RC隔离级别时,每次发起查询操作时,都重新生成一个ReadView

三、RR基于ReadView的实现

RR隔离级别:事务读一条数据,无论读多少次,都是一个值,别的事务修改数据之后哪怕提交了,你也看不到人家修改的值,这样就避免了不可重复读的问题。

同时如果别的事务插入了一些新的数据,你也是读不到的,这样就可以避免幻读问题了(不一定)。

事务处于RR隔离级别时,只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了

四、MVCC能否完全解决幻读?

如下场景:

# 事务T1,REPEATABLE READ隔离级别下
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT * FROM t_test WHERE id = 1;
Empty set (0.01 sec)

# 此时事务T2执行了:INSERT INTO t_test VALUES(1, '呵呵'); 并提交

mysql> UPDATE t_test SET name = '哈哈' WHERE id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> SELECT * FROM t_test WHERE id = 1;
+--------+---------+
| id  |  name    |
+--------+---------+
|     1 |   哈哈   |
+--------+---------
1 row in set (0.01 sec)

在REPEATABLE READ隔离级别下,T1第一次执行普通的SELECT语句时生成了一个ReadView,之后T2向表中新插入了一条记录便提交了,ReadView并不能阻止T1执行UPDATE或者DELETE语句来对改动这个新插入的记录(因为T2已经提交,改动该记录并不会造成阻塞),但是这样一来这条新记录的trx_id隐藏列就变成了T1的事务id

之后T1中再使用普通的SELECT语句去查询这条记录时就可以看到这条记录了,也就把这条记录返回给客户端了。因为这个特殊现象的存在,可以认为InnoDB中的MVCC并不能完完全全的禁止幻读。

解决幻读分为两种,一种是当前读,这种需要通过next-key lock解决,一种是快照读,快照读可以使用MVCC解决,但如果本事务发起更新操作时,那么幻读问题就会再现。

当前读:更新数据时先读后写,这个读只能是当前的值,所以能读到事务T2插入的值,而读了之后再修改记录,那么记录的`trx_id`也被更新成事务T1的事务ID,因此接下来SELECT就可以得到这条记录。

五、小结

MVCC机制:多版本并发控制机制,专门控制多个事务并发运行的时候,互相之间会如何影响。

多个事务并发运行的时候,同时读写一个数据,可能会出现脏写、脏读、不可重复读、幻读几个问题。MySQL实现MVCC机制的时候,是基于undo log多版本链条 + Read View机制来做的,MySQL默认隔离级别是RR,就是基于这套机制来实现的。

解决幻读分为两种:

  • 当前读:这种需要通过next-key lock解决
  • 快照读:快照读可以使用MVCC解决,如果本事务发起更新操作时(发生当前读),那么幻读问题就会再现。

,MySQL默认隔离级别是RR,就是基于这套机制来实现的。

解决幻读分为两种:

  • 当前读:这种需要通过next-key lock解决
  • 快照读:快照读可以使用MVCC解决,如果本事务发起更新操作时(发生当前读),那么幻读问题就会再现。

以上是关于MySQL ---- MVCC的主要内容,如果未能解决你的问题,请参考以下文章

MySQL——MySQL InnoDB的MVCC实现机制

MySQL——MySQL InnoDB的MVCC实现机制

MYSQL MVCC实现原理

Mysql系列--MVCC机制

MySQL的MVCC底层原理

深入理解MySQL的MVCC原理