MVCC,即多版本并发控制(Multi-Version Concurrency Control)指的是,通过版本链维护一个数据的多个版本,使得读写操作没有冲突,可保证不同事务读写、写读操作并发执行,提高系统性能。

实际上,innodb中“读已提交”和“可重复读”这两种隔离级别的事务在查询数据时访问版本链的过程,是基于这套原理。本文将总结MVCC机制底层原理,并解释它是如何解决“脏读”和“不可重复读”问题的。

感觉现在每总结一个知识点,总是会引出一堆相关知识,学习真的是永无止境~。首先介绍一下几种并发事务问题,和四种隔离级别,这与后文原理介绍密不可分。而且,毕竟都是面试高频考点,尊重一下。

并发事务带来的问题

  • 脏读:表示一个事务读到另一个事务未提交的数据。若另一个事务回滚,那本事务读到的数据跟数据库中的不一致;
  • 可重复读:表示一个事务读到另一个事务已提交的数据。本事务在另一个事务提交前和提交后读到的数据不一致;
  • 幻读:其它事务插入数据的前后,当前事务两次读取的数据不一致;
  • 丢弃修改:两个事务先同时读取一个数据,读到一样的数据,然后事务一先修改,事务二再修改,事务一的修改被丢弃。

事务的四种隔离级别

  • 读未提交 READ-UNCOMMITTED:一个事务能读到其它事务未提交的数据,即脏读。也会出现不可重复读和幻读。
  • 读已提交 READ-COMMITTED:一个事务只能读到其它事务已提交的数据,不会出现脏读,但是有幻读和不可重复读
    • 其它事务提交修改语句的前后,当前事务两次读取的数据可能不一样。不称之为,不可重复读;
    • 其它事务提交插入语句前后,当前事务可能会把新插入的数据也读出来。称之为,幻读;
  • 可重复读 REPEATABLE-READ(MySQL默认使用的隔离级别):对一个数据读取多次记录是相同的。sql标准里,REPEATABLE-READ禁止了脏读和不可重复读,可能会有“幻读”。但是MySQL中REPEATABLE-READ也禁止了幻读
  • 串行化 SERIALIZABLE:前三种都允许读-读、读-写、写-读的并发操作,但SERIALIZABLE中不允许读-写、写-读的并发操作,而是串行的,不会出现各种问题

innodb中采用了next-key-lock锁算法避免了幻读,使得“可重复读”级别也达到了“串行化”级别的效果

MVCC机制

我们先设定一个场景:

假设数据库表中存在一条记录row_old,这时事务A和事务B同时begin,事务A将该记录修改为了row_new,事务B读取行记录,事务A提交,事务B再次读取这条行记录。

本文中将使用该场景来分析“脏读”和“不可重复读”现象。

若事务B在A提交前读到row_new,即出现“脏读”现象;若事务B在A提交后读到row_new,即出现“不可重复读”现象。

但是,正常情况是,无论事务A是否提交,事务B读取该条记录,都只能读出row_old。

什么方法可以达到这种效果呢?可以很直观地想到,将事务A修改后的版本存起来。那么又有一系列问题,如何存,用什么结构来存?版本链便是为此而引入的。

版本链

版本链,实际上就是一条存储多个版本行记录的链表。数据库中的每一行数据都对应一个版本链。链表中每一个结点代表一个行记录。行记录中有两个重要的隐藏字段:

  • trx_id:记录修改成当前版本的事务编号;
  • roll_pointer:指向上一个版本的指针,即回滚指针。

版本链的最底层即为数据表中最原始的行记录,上层存储各个事务修改后的行记录,逐个用回滚指针相连接。版本链示意图如下所示:



还有一个问题,版本链是存储在哪的?没错,我们熟悉的undo log回滚日志就是用来存储版本链的 。

一致性视图

如果当前事务修改一条记录,这条更新过的记录被记录到版本链中,对于当前事务而言,由于自身事务id和版本链中最新一条行记录的trx_id相匹配,所以可以将其读取出来。但是对于其它事务而言,是不希望能读出这条记录的,而是希望它能顺着版本链,找出自己需要的版本的行记录。

那么如何找到正确的版本?这里涉及到一个快照机制。事务在执行select语句时,会生成一个一致性视图:read-view,相当于一个快照,记录正在活跃的事务的编号。

read-view里面包含一个数组,m_ids,该数组记录(产生快照的这一时刻)版本链中未提交的每个版本的trx_id组成的序列。同时,read-view还会记录一个最大已创建事务id,即 max_id,以及数组中最小id即 min_id。查询版本链时,会将行记录中的trx_id与read-view中的max_id、min_id、m_ids[]等进行比对。依据如下版本比对规则来进行比对。

版本链比对规则

  1. 如果trx_id小于min_id,说明该版本是已提交事务生成的,数据可见;
  2. 如果trx_id大于max_id,说明该版本是将来启动的事务生成的,数据不可见;
  3. 如果min_id<=trx_id<=max_id,就包括两种情况:
    • trx_id在m_ids数组中:表示这个版本是未提交事务生成的,数据不可见,本事务可见;
    • trx_id不在m_ids数组中:表示这个版本是已提交事务生成的,数据可见。

补充:删除的原理

删除可以认为是update的特殊情况。假如要删除一行记录,会将版本链上最新一条记录复制一份,将行格式头信息中(record header)里面的(deleted flag)标志位置为true,表示当前记录已被删除。若顺着版本链访问到这条记录,(deleted flag)标志位为true,表示记录已删除,不返回数据。

相关分析

“脏读”分析

让我们再回到前文提到的场景:事务A将行记录row_old修改为了row_new,未提交时,row_new行记录已经加入到了版本链,并且记录了事务A的id。此时事务B开始查询,生成快照read-view,其中的m_ids记录了未提交版本的trx_id,包括row_new的id。当查询到row_new时,其trx_id在m_ids数组中,根据版本链比对规则,其对B事务不可见,因此继续向下查找,直到找出row_old。

综上所述,read-view快照机制加上版本链匹配规则,可以杜绝“脏读”现象。

“读已提交”和“可重复读”区别

根据上文的分析,我们对MVCC机制有了一个清晰的了解。在“读已提交”隔离级别就是基于这个原理来解决“脏读”问题的。而“可重复读”隔离级别却与之不尽相同,差别如下:

  • 读已提交:每次select时都会生成一个readView;
  • 可重复读:只在事务的第一次select操作前生成一个readView,之后的查询都重复使用这个readView。

“不可重复读”分析

再次回到上文中提到的情景,假设事务A修改将row_old修改为row_new,未提交时,事务B开始执行select,生成read-view,这时事务A进行提交,然后事务B再次select,这时依然沿用上一次的read-view,row_new的id依然是记录在m_ids数组中的,所以事务B只能读取到row_old,两次读取都只能读出row_old。

这里我希望再补充一种情况:B事务尚未提交结束时,再开启一个事务C,修改row_new为row_new_c,并提交,这时版本链中新增一个row_new_c结点,记录C的id。事务B再次select,依然只能读取到row_old。因为在版本链中遍历至row_new_c时,会触发“版本对比规则”的第二条,该条记录对事务B不可见,因此继续向下查找直到找出row_old。

所以,综上所述,无论版本链发生何种改变,只要在单次事务中read-view固定不变,读取到的数据一定是维持在同一个版本。在“可重复读”级别中,就是通过沿用第一次read-view快照的方法,解决了“不可重复读”问题。

MySQL多版本并发控制——MVCC机制分析的更多相关文章

  1. MySQL多版本并发控制(MVCC)

    MVCC是行级锁的一个变种,但是它在很多的情况下避免了加锁操作,因此开销更低.MySQL,包括Oracle.PostgreSQL都实现了MVCC,虽然每个关系数据库实现不一样,但大都是实现了非阻塞的读 ...

  2. MySQL多版本并发控制机制(MVCC)-源码浅析

    MySQL多版本并发控制机制(MVCC)-源码浅析 前言 作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎,但感觉还是不够过瘾.<<事务处理-概念与技术>>诚然 ...

  3. Mysql InnoDB多版本并发控制MVCC

    参考书籍<mysql是怎样运行的> 系列文章目录和关于我 一丶为什么需要事务隔离级别 mysql是一个客户端/服务断软件,对于同一个服务器来说,可以有多个客户端进行连接,每一个客户端进行连 ...

  4. 转: 多版本并发控制(MVCC)在分布式系统中的应用 (from coolshell)

    from:  http://coolshell.cn/articles/6790.html 问题 最近项目中遇到了一个分布式系统的并发控制问题.该问题可以抽象为:某分布式系统由一个数据中心D和若干业务 ...

  5. SQL事务的四种隔离级别和MySQL多版本并发控制

      SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的那些改变时可见的,那些是不可见的.低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销. ReadUncommitted( ...

  6. MySQL——多版本并发控制

    核心心知识点: (1)MVCC的优点和缺点 (2)MVCC的工作机制 之前在提及幻读的时候,提到过InnoDB的多版本并发控制可以解决幻读问题. 大多数MySQL的事务性存储引擎,例如InnoDB.F ...

  7. 数据库原理-事务隔离与多版本并发控制(MVCC)

    刚来美团实习,正好是星期天,不得不说,其内部的资料很丰富,看了部分文档后,对数据库事务这块更理解了.数据库事务的ACID,大家都知道,为了维护这些性质,主要是隔离性和一致性,一般使用加锁这种方式.同时 ...

  8. 多版本并发控制 MVCC

    介绍多版本并发控制 多版本并发控制技术(Multiversion Concurrency Control,MVCC) 技术是为了解决问题而生的,通过 MVCC 我们可以解决以下几个问题: 读写之间阻塞 ...

  9. MySQL 多版本并发控制(MVCC)

    可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁的操作,因此开销会很低.主要实现的是非阻塞的读操作,写操作也只是锁定必要的行.MVCC的实现是通过保存数据在某个时间点的快照来实现的,也 ...

随机推荐

  1. 题解-CF708D Incorrect Flow

    题面 CF708D Incorrect Flow 给一张网络流图,可能有流量不守恒或者流量超过容量的情况,求最少的将某条边流量或容量 \(\pm 1\) 的操作次数使得网络流图正确. 数据范围:\(1 ...

  2. TMOOC 1969 开锁

    update on 2020.2.28 时隔近日重新想这道题,其实复杂度正确的解法是 可持久化 01 Trie. 考虑对于每一个 \(a[i]\),考虑能将它作为最大值的最大包容区间 \([l, r] ...

  3. 三、java多线程核心技术(笔记)——线程的优先级

    概论: 在操作系统中,线程可以划分优先级,优先级高的获得的CPU资源较多,也就是CPU优先执行优先级较高的线程.在JAVA中线程的优先级 分1~~10个10个等级.大于或者小于会报异常. 一.线程优先 ...

  4. 封装事件订阅来进行非父子组件的传值(React)

    const list={} // 将事件名和事件函数装进事件池里 function $on(name,func) { if(!name || !func) return; if(!Object.key ...

  5. antDesign获取表单组件的值

    子组件中:  getFormValue是在点击确定按钮获取表单值得事件函数,一旦执行就会执行里边的validate()回调函数 返回的数据中有error和value两种,如果存在error那就是其中某 ...

  6. MySql字符集与排序规则详解

    前段时间往MySQL中存入emoji表情或生僻字.繁体字时,报错无法添加,研究后发现这是字符集编码的问题,今天就来分析一下各个字符集与排序规则 一.字符集 先说字符,字符是各种文字和符号的总称,包括各 ...

  7. 学好Spark/Kafka必须要掌握的Scala技术点(二)类、单例/伴生对象、继承和trait,模式匹配、样例类(case class)

    3. 类.对象.继承和trait 3.1 类 3.1.1 类的定义 Scala中,可以在类中定义类.以在函数中定义函数.可以在类中定义object:可以在函数中定义类,类成员的缺省访问级别是:publ ...

  8. Java中中文排序器

    在Java中使用Collator类按照汉字拼音排序字符串 public static void main(String[] args) throws Exception{ String[] strs ...

  9. Flink连接器-批处理-读写Hbase

    Flink批处理与hbase的读写 source-hbase 父类 是模仿官方写的. import org.apache.flink.api.common.io.LocatableInputSplit ...

  10. JAVA基础--cmd命令

    常用cmd命令行,很简单 md(make directory):创建文件夹 如:md test1: rd(remove directory):删除空文件夹 如:rd test1:若文件夹包含其他文件, ...