MySQL多版本并发控制MVCC详解

目录
  • 1.什么是MVCC
  • 2快照读与当前读
    • 2.1 快照读
    • 2.2当前读
  • 3.复习
    • 3.1 再谈隔离级别
    • 3.2 隐藏字段、Undo Log版本链
  • 4、MVCC实现原理之ReadView
    • 4.1什么是ReadView
    • 4.2 设计思路
    • 4.3 ReadView的规则
  • 5.举例说明
    • 5.1 READ COMMITTED
    • 5.2 REPEATABLE READ
    • 5.3 如何解决幻读
  • 6.总结

1.什么是MVCC

MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC是通过数据行的多个版本管理来实现数据库的并发控制。这项技术使得在InnoDB的事务隔离级别下执行一致性读.操作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。

MVCC没有正式的标准,在不同的DBMS中MVCC的实现方式可能是不同的,也不是普遍使用的(大家可以参考相关的DBMS文档)。这里讲解InnoDB中 MVCC的实现机制(MySQL其它的存储引擎并不支持它)

2快照读与当前读

MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁非阻塞并发读,而这个读指的就是快照读,而非当前读。当前读实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。

2.1 快照读

快照读又叫一致性读,读取的是快照数据。不加锁的简单的SELECT都属于快照读即不加锁的非阻塞读;比如这样:

select * from player where ...

之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。

既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。

2.2当前读

当前读读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的SELECT,或者对数据进行增删改都会进行当前读。比如:

3.复习

3.1 再谈隔离级别

我们知道事务有4个隔离级别,可能存在三种并发问题:

在MysQL 中,默认的隔离级别是可重复读,可以解决脏读和不可重复读的问题,如果仅从定义的角度来看,它并不能解决幻读问题。如果我们想要解决幻读问题,就需要采用串行化的方式,也就是将隔离级别提升到最高,但这样一来就会大幅降低数据库的事务并发能力。

MVCC可以不采用锁机制,而是通过乐观锁的方式来解决不可重复读和幻读问题!它可以在大多数情况下替代行级锁,降低系统的开销。

3.2 隐藏字段、Undo Log版本链

回顾一下undo日志的版本链,对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列。

trx_id:每次一个事务对某条聚簇索引记录进行改动时,都会把该事务的事务id赋值给trx_id隐藏列。roll_pointer:每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到undo日志中,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息。

4、MVCC实现原理之ReadView

MVCC的实现依赖于:隐藏字段、Undo Log、Read View

4.1什么是ReadView

在MVCC机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在Undo Log里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到ReadView了,它帮我们解决了行的可见性问题。

ReadView就是事务在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB为每个事务构造了一个数组,用来记录并维护系统当前活跃事务的lD(“"活跃"指的就是,启动了但还没提交)。

4.2 设计思路

使用READ UNCOMMITTED隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。

使用SERIALIZABLE隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。

使用READ COMMITTEDREPEATABLE READ隔离级别的事务,都必须保证读到已经提交了的事务修改过的记录。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。

这个ReadView中主要包含4个比较重要的内容,分别如下:

4.3 ReadView的规则

有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见。

  • 如果被访问版本的trx_id属性值与ReadView中的creator_trx_id值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值小于ReadView中的up_limit_id值,表明生成该版本的事务在当前事务生成ReadView前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的trx_id属性值大于或等于ReadView中的low_limit_id值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的trx_id属性值在ReadView的up_limit_idlow_limit_id之间,那就需要判断一下trx_id属性值是不是在trx_ids列表中。如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问。如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。 4.4 MVCC整体操作流程

了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:

  • 1.首先获取事务自己的版本号,也就是事务ID;
  • 2.获取ReadView;
  • 3.查询得到的数据,然后与ReadView中的事务版本号进行比较;
  • 4.如果不符合Readview规则,就需要从Undo Log中获取历史快照;
  • 5.最后返回符合规则的数据。

如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录。

InnoDB中,MVCC是通过Undo Log + Read View进行数据读取,Undo Log保存了历史快照,而Read View规则帮我们判断当前版本的数据是否可见。

在隔离级别为读已提交(Read Committed)时,一个事务中的每一次select查询都会重新获取一次Read View。

当隔离级别为可重读的时候,就避免了不可重复读,这是因为一个事务只在第一次select的时候会获取一次Read View,而后面所有的select都会复用这个Read View,

如下所示:

5.举例说明

假设现在student表中只有一条由事务id8事务插入一条记录:

MVCC只能在READ COMMITTED和REPEATABLE READ两个隔离级别下工作。接下来看一下READ COMMITTEDREPEATABLE READ所谓的生成Readview的时机不同到底不同在哪里。

5.1 READ COMMITTED

隔离级别下:

READ COMMITTED:每次读取数据前都生成一个ReadView

现在有两个事务id分别为10、20的事务在执行:

说明:事务执行过程中,只有在第一次真正修改记录时(比如使用INSERT、DELETE、UPDATE语句),才会被分配一个单独的事务id,这个事务id是递增的。所以我们才在事务2中更新一些别的表的记录,目的是让它分配事务id。

此刻,表student中id为1的记录得到的版本链表如下所示:

假设现在有一个使用READ COMMITED隔离级别的事务开始执行:

这个SELECT1的执行过程如下:

步骤1∶在执行SELECT语句时会先生成一个ReadView ,ReadView的trx_ids列表的内容就是[10,20],up_limit_id10, low_limit_id21 , creator_trx_id0
步骤2:从版本链中挑选可见的记录,从图中看出,最新版本的列name的内容是'王五',该版本的trx_id值为10,在trx_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
步骤3:下一个版本的列name的内容是'李四',该版本的trx_id值也为10,也在trx_ids列表内,所以也不符合要求,继续跳到下一个版本。
步骤4:下一个版本的列 name的内容是‘张三',该版本的trx_id值为8,小于ReadView 中的up_limit_id值10,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为‘张三’的记录。

之后,我们把事务id10的事务提交一下:

然后再到事务id20的事务中更新一下表studentid1的记录:

此刻,表student中id为1的记录的版本链就长这样:

然后再到刚才使用READ COMMITTED隔离级别的事务中继续查找id为1的记录,如下:

这个SELECT2的执行过程如下:

步骤1∶在执行SELECT语句时会又会单独生成一个ReadView,该ReadView的trx_ids列表的内容就是[20],up_limit_id为20,low_limit_id为21, creator_trx_id为0。
步骤2:从版本链中挑选可见的记录,从图中看出,最新版本的列name的内容是‘宋八’,该版本的tr×_id值为20,在trx_ids列表内,所以不符合可见性要求,根据roll.pointer跳到下一个版本。
步骤3∶下一个版本的列name的内容是’钱七’,该版本的trx_id值为20,也在trx_ids列表内,所以也不符合要求,继续跳到下一个版本。
步骤4∶下一个版本的列name的内容是’王五’,该版本的trx_id值为10,小于ReadView中的up_limit.id值20,所以这个版本是符合要求的,最后返回给用户的版本就是这条列name为’王五’的记录。
以此类推,如果之后事务id为20的记录也提交了,再次在使用READ CONMMITTED隔离级别的事务中查询表student中id值为1的记录时,得到的结果就是‘宋八’了,具体流程我们就不分析了。

5.2 REPEATABLE READ

隔离级别下:

使用REPEATABLE READ隔离级别的事务来说,只会在第一次执行查询语句时生成一个ReadView,之后的查询就不会重复生成了。

比如,系统里有两个事务id分别为10、20的事务在执行:

此刻,表student中id为1的记录得到的版本链表如下所示:

假设现在有一个使用REPEATABLE READ隔离级别的事务开始执行:

此时执行过程与read committed相同

然后再到刚才使用REPEATABLE READ隔离级别的事务中继续查找id为1的记录,如下:

这个SELECT2的执行过程如下:

步骤1:因为当前事务的隔离级别为REPEATABLE READ,而之前在执行SELECT1时已经生成过ReadView了,所以此时直接复用之前的ReadView,之前的ReadView的trx_ids列表的内容就是[10,20],up_limit_id为10, low_limit_id为21 , creator_trx_id为0。
步骤2:然后从版本链中挑选可见的记录,从图中可以看出,最新版本的列name的内容是’宋八’trx_id值为20,在trx_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。
步骤3:下一个版本的列name的内容是’钱七’,该版本的trx_id值为20,也在trx_ids列表内合要求,继续跳到下一个版本。
步骤4:下一个版本的列name的内容是’王五’,该版本的trx_id值为10,而trx_ids列表中是包含值为10的事务id的,所以该版本也不符合要求,同理下一个列name的内容是’李四’的版本也不符合要求。继续跳到下个版本。
步聚5∶下一个版本的列name的内容是’张三’,该版本的trx_id值为80,小于Readview中的up_limit_id值10,所以这个版本是符合要求的,最后返回给用户的版本就是这条列c为‘张三’的记录。
两次SELECT查询得到的结果是重复的,记录的列c值都是’张三’,这就是可重复读的含义。如果我们之后再把事务id为20的记录提交了,然后再到刚才使用REPEATABLE READ隔离级刷的事务中继续查找这个id为1的记录,得到的结果还是’张三’,具体执行过程大家可以自己分析一下。

5.3 如何解决幻读

假设现在表student中只有一条数据,数据内容中,主键id=1,隐藏的trx_id=10,它的undo log如下图所示。

假设现在有事务A和事务B并发执行,事务A的事务id为20,事务B的事务id为30。
步骤1:事务A开始第一次查询数据,查询的SQL语句如下。

select * from student where id > 1;

在开始查询之前,MySQL会为事务A产生一个ReadView,此时ReadView的内容如下: trx_ids=[20, 30 ] ,up_limit_id=20 , low_limit_id=31 , creator_trx_id=20。

由于此时表student中只有一条数据,且符合where id>=1条件,因此会查询出来。然后根据ReadView机制,发现该行数据的trx_id=10,小于事务A的ReadView里up_limit_id,这表示这条数据是事务A开启之前,其他事务就已经提交了的数据,因此事务A可以读取到。

结论:事务A的第一次查询,能读取到一条数据,id=1。

步骤2∶接着事务B(trx_id=30),往表student中新插入两条数据,并提交事务。

insert into student(id,name) values(2,'李四');
insert into student(id,name) values(3,'王五');

此时表student中就有三条数据了,对应的undo如下图所示:

步骤3∶接着事务A开启第二次查询,根据可重复读隔离级别的规则,此时事务A并不会再重新生成ReadView。此时表student中的3条数据都满足 where id>=1的条件,因此会先查出来。然后根据ReadView机制,判断每条数据是不是都可以被事务A看到。
1)首先 id=1的这条数据,前面已经说过了,可以被事务A看到。
2)然后是id=2的数据,它的trx_id=30,此时事务A发现,这个值处于up_limit_id和low_limit_id之间,因此还需要再判断30是否处于trx_ids数组内。由于事务A的trx_ids=[20,30],因此在数组内,这表示id=2的这条数据是与事务A在同一时刻启动的其他事务提交的,所以这条数据不能让事务A看到。
3)同理,id=3的这条数据, trx_id 也为30,因此也不能被事务A看见。

结论:最终事务A的第二次查询,只能查询出id=1的这条数据。这和事务A的第一次查询的结果是一样的,因此没有出现幻读现象,所以说在 MySQL的可重复续隔离级别下,不存在幻读问题。

6.总结

这里介绍了MVCCREAD COMNNITTDREPEATABLE READ这两种隔离级别的事务在执行快照读操作时访问记录的版本链的过程。这样使不同事务的读-写写-读操作并发执行,从而提升系统性能。

核心点在于ReadView的原理,READ CONMITTD、REPEATABLE READ这两个隔离级别的一个很大不同就是生成ReadView的时机不同:

READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadViewREPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个Readview就好了。

通过MVCC我们可以解决:

  • 1.读写之间阻塞的问题。通过MVcc可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。
  • 2.降低了死锁的概率。这是因为MVCC采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。
  • 3.解次快照读的问题。当我们查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。

到此这篇关于MySQL多版本并发控制MVCC详解的文章就介绍到这了,更多相关MySQL MVCC内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • mysql多版本并发控制MVCC的实现

    事务隔离级别设置 set global transaction isolation level read committed; //全局的 set session transaction isolation level read committed; //当前会话 修改事务提交方式(是否自动提交,mysql默认自动提交) SET AUTOCOMMIT = 1; //自动提交,为0手动提交 不同数据库引擎MVCC模式各不相同,典型有乐观和悲观并发控制. innodb 说明: InnoDB的MVCC

  • 详解MySQL多版本并发控制机制(MVCC)源码

    目录 一.前言 二.MVCC(多版本并发控制机制) 2.1.Repeatable Read 2.2.Read Commit 2.3.MVCC的优势 三.MVCC(实现机制) 3.1.select运行栈 3.2.read_view的创建过程 3.3.行版本可见性 3.4.undolog搜索可见版本的过程 3.5.read_view创建时机再讨论 四.MVCC和锁的同时作用导致的一些现象 五.总结 一.前言 作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾.<<

  • MySQL多版本并发控制MVCC深入学习

    MVCC MVCC(Multi-Version Concurrency Control),即多版本并发控制.是 innodb 实现事务并发与回滚的重要功能.锁机制可以控制并发操作,但是其系统开销较大,而MVCC可以在大多数情况下代替行级锁,使用MVCC,能降低其系统开销. 具体实现是在数据库的每一行中,额外添加三个字段: DB_TRX_ID : 记录插入或更新该行的最后一个事务的事务ID DB_ROLL_PTR : 指向改行对应undolog 的指针 DB_ROW_ID : 单调递增的ID,他就

  • MySQL的多版本并发控制MVCC的实现

    目录 什么是MVCC MVCC的实现 MVCC 有没有解决幻读? 什么是MVCC MVCC就是多版本并发控制. MySQL的事务型存储引擎通过多版本并发控制(MVCC)来提升并发性能. 可以认为MVCC是行级锁的一个变种,但是它在大多数情况下避免了加锁操作,同时实现非阻塞的读操作,因此开销更低. MVCC是通过保存数据在某个时间点的快照来实现的,核心思想就是保存数据的历史版本,通过对数据行的多个版本管理来实现数据库的并发控制. 这样我们就可以通过比较版本号决定数据是否显示出来,读取数据的时候不需

  • mysql的MVCC多版本并发控制的实现

    1 什么是MVCC MVCC全称是: Multiversion concurrency control,多版本并发控制,提供并发访问数据库时,对事务内读取的到的内存做处理,用来避免写操作堵塞读操作的并发问题. 举个例子,程序员A正在读数据库中某些内容,而程序员B正在给这些内容做修改(假设是在一个事务内修改,大概持续10s左右),A在这10s内 则可能看到一个不一致的数据,在B没有提交前,如何让A能够一直读到的数据都是一致的呢? 有几种处理方法,第一种: 基于锁的并发控制,程序员B开始修改数据时,

  • Mysql MVCC多版本并发控制详情

    目录 1.MVCC 2.当前读 3.快照读(提高数据库的并发查询能力) 4.当前读.快照读.MVCC关系 5.MVCC实现原理 6.MVCC核心思想 1.MVCC MVCC,全称Multi-Version Concurrency Control,即多版本并发控制.MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存. MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有>读写冲突时,也能做

  • MySQL多版本并发控制MVCC底层原理解析

    目录 1 事务并发中遇到的问题 1.1 脏读 1.2 不可重复读 1.3 幻读 2 隔离级别 3 版本链 4 ReadView 4.1 ReadView 定义 4.2 访问控制 4.3 再谈隔离 4.3.1 READ COMMITTED(读已提交) 4.3.2 REPEATABLE READ(可重读) 5 幻读 6 总结 1 事务并发中遇到的问题 1.1 脏读 当一个事务读取到了另外一个事务修改但未提交的数据,被称为脏读. 1.2 不可重复读 当事务内相同的记录被检索两次,且两次得到的结果不同时

  • MySQL多版本并发控制MVCC详解

    目录 1.什么是MVCC 2快照读与当前读 2.1 快照读 2.2当前读 3.复习 3.1 再谈隔离级别 3.2 隐藏字段.Undo Log版本链 4.MVCC实现原理之ReadView 4.1什么是ReadView 4.2 设计思路 4.3 ReadView的规则 5.举例说明 5.1 READ COMMITTED 5.2 REPEATABLE READ 5.3 如何解决幻读 6.总结 1.什么是MVCC MVCC (Multiversion Concurrency Control),多版本并

  • Mysql InnoDB多版本并发控制MVCC详解

    目录 一丶为什么需要事务隔离级别 1.实现事务隔离的方式:串行执行 2.实现事务隔离的方式:可串行执行 二丶并发事务执行的问题:脏写,脏读,不可重复读,幻读 1.脏写 2.脏读 3.不可重复读 4.幻读 三丶隔离级别 1.Read UnCommitted 读未提交 2.Read Committed 读已提交 3.Repeatable Read 可重复读 4.Serializable 可串行化 四丶Mysql设置隔离级别 1.设置全局隔离级别 2.设置会话隔离级别 3.设置下一个事务的隔离级别 4

  • MySQL事务管理的作用详解

    目录 1.为何使用事务管理 2.数据库事务的原理 3.什么是事务 3.1 事务的特性ACID 3.2 事务的并发问题 3.3 隔离级别 4.Spring事务管理 1.为何使用事务管理 可以保证数据的完整性.事务(Transaction),就是将一组SQL语句放在同一批次内去执行,如果一个SQL语句出错,则该批次内 的所有SQL都将被取消执行. 例子: 转账为例. 金庸向张无忌转账1000元.----在数据库中修改两个账号的余额. 发生意外情况,则出现金庸减钱成功,而张无忌加钱失败. 如何解决?

  • CentOS 6.6 源码编译安装MySQL 5.7.18教程详解

    一.添加用户和组 1.添加mysql用户组 # groupadd mysql 2.添加mysql用户 # useradd -g mysql -s /bin/nologin mysql -M 二.查看系统中是否安装mysql,如果安装需要卸载 # rpm -qa | grep mysql mysql-libs-5.1.73-3.el6_5.x86_64 # rpm -e mysql-libs-5.1.73-3.el6_5.x86_64 --nodeps 三.安装所需依赖包 # yum -y ins

  • MySQL交换分区的实例详解

    MySQL交换分区的实例详解 前言 在介绍交换分区之前,我们先了解一下 mysql 分区. 数据库的分区有两种:水平分区和垂直分区.而MySQL暂时不支持垂直分区,因此接下来说的都是水平分区.水平分区即:以行为单位对表进行分区.比如:按照时间分区,每一年一个分区等. 在MySQL中,分区是可以交换的,可以将一个分区表中的一个分区和一个普通表中的数据互换. 交换分区的实现 1.交换分区的语法 alter table pt exchange partition p with table nt; 解释

随机推荐