前言

MySQL中大名鼎鼎的MVCC机制想必大家都有所耳闻吧,虽然在平时MySQL使用过程中基本上用不到,但是面试中出场率十分高,而且作为架构师的你也是需要知道它的工作机制。那么你对MVCC机制了解多少呢?MVCC机制是用来干嘛的呢?底层的工作原理是怎么样的呢?本文就带你一探究竟。

MVCC机制是什么?

MVCC,英文全称Multiversion Concurrency Control,多版本并发控制。简单理解,就是相当于给我们的MySQL数据库拍个“快照”,定格某个时刻数据库的状态。

那你可能问为什么要拍个“快照”,也就是MVCC机制?

还记得事务的一大特性就是隔离性,一共有4个隔离级别,读未提交,读已提交,可重复读,串行化。

​以MySQL InnoDB 引擎的默认隔离级别可重复读为例,可重复读指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的。

关于事务的基本特性请移步一文带你理解MySQL事务核心知识点

为了保证事务启动到结束整个生命周期看到的数据是一致的, 一般有两种方案:

  1. MySQL对数据“读-写”的时候,加锁,其他事务写这条数据时加上锁,其他事务读取的时候阻塞。

  2. MySQL可以对事务启动的时候,对数据库拍个“快照”,那么事务运行过程中读取都从这个快照读取,不也是保证数据一致么。

第一种方案存在明显的问题,加锁会引发阻塞,从而降低数据库性能。而MySQL设计者们采用第二种,也就是大名鼎鼎的MVCC,它不仅能够解决不可重复读,还一定程度解决幻读的问题,因为你整个数据库快照都有了,你就知道那个时刻的数据了。

虽然说SQL标准定义中可重复读隔离级别下会存在幻读的现象,但是不同的数据库厂商可以基于SQL标准下有不同的实现,那么不同隔离级别下发生的现象也会有出入,就拿MySQL的可重复读隔离级别就可以一定程度保证幻读。

小结一下:

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

什么是快照读和当前读?

前面提到了快照读和当前读,这又有什么不一样呢,什么样的sql语句算是快照读,什么样的又算是当前读呢?

快照读

快照读又叫普通读,也就是利用MVCC机制读取快照中的数据。不加锁的简单的SELECT 都属于快照读,比如这样:

SELECT * FROM user WHERE ... 复制代码
  • 快照读是基于MVCC实现的,提高了并发的性能,降低开销

  • 大部分业务代码中的读取都属于快照读

当前读

当前读读取的是记录的最新版本,读取时会对读取的记录进行加锁, 其他事务就有可能阻塞。加锁的 SELECT,或者对数据进行增删改都会进行当前读。比如:

SELECT * FROM user LOCK IN SHARE MODE; # 共享锁 SELECT * FROM user FOR UPDATE; # 排他锁 INSERT INTO user values ... # 排他锁 DELETE FROM user WHERE ... # 排他锁 UPDATE user SET ... # 排他锁 复制代码
  • update、delete、insert语句虽然没有select, 但是它们也会先进行读取,而且只能读取最新版本。

MVCC机制是咋工作的呢?

前面打个比方说MVCC机制相当于是基于整个数据库“拍了个快照”,这时,你会说这看上去不太现实啊。如果一个库有 100G,那么我启动一个事务,MySQL 就要保存 100G 的数据出来,这个过程得多慢啊,而且也很占用空间啊,根本就不能支持几个事务啊。别急,我们现在来讲解下MVCC机制是如何工作的。

数据的多个版本

首先MySQL innoDB存储引擎需要支持一条数据可以保留多个历史版本。怎么保留呢?还记得事务日志undo log吗?

undo log保存了数据的各个历史版本,用于数据的回滚,保证事务的一致性。详情查看详解MySQL事务日志——undo log

对于使用 InnoDB 存储引擎的数据库表,它的聚簇索引记录中都包含下面两个隐藏列:

  • trx_id,当一个事务对某条聚簇索引记录进行改动时,就会把该事务的事务 id 记录在 trx_id 隐藏列里;

  • roll_pointer,每次对某条聚簇索引记录进行改动时,都会把旧版本的记录写入到 undo 日志中,然后这个隐藏列是个指针,指向每一个旧版本记录,于是就可以通过它找到修改前的记录。

InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。

​如上图所示,针对id=1的这条数据,都会将旧值放到一条undo日志中,就算是该记录的一个旧版本,随着更新次数的增多,所有的版本都会被 roll_pointer 属性连接成一个链表,我们把这个链表称之为版本链,根据版本链就可以找到这条数据历史的版本。

一致性视图ReadView

利用undo log日志我们已经保留下了数据的各个版本,那么现在关键的问题是要读取哪个版本的数据呢?

这时就需要用到ReadView了,ReadView就是事务在使用MVCC机制进行快照读操作时产生的一致性视图, 比如针对可重复读隔离级别,是在事务启动的时候,创建一个ReadView, 那ReadView种都有哪些关键信息呢?

  • trx_ids: 指的是在创建 ReadView 时,当前数据库中「活跃事务」的事务 id 列表,注意是一个列表, “活跃事务”指的就是,启动了但还没提交的事务。

  • min_trx_id: 指的是在创建 ReadView 时,当前数据库中「活跃事务」中事务 id 最小的事务,也就是 m_ids 的最小值。

  • max_trx_id:这个并不是 m_ids 的最大值,而是创建 ReadView 时当前数据库中应该给下一个事务的 id 值,也就是全局事务中最大的事务 id 值 + 1;

  • creator_trx_id :指的是创建该 ReadView 的事务的事务 id, 只有在对表中的记录做改动时(执行INSERT、DELETE、UPDATE这些语句时)才会为 事务分配事务id,否则在一个只读事务中的事务id值都默认为0。

​对于当前事务的启动瞬间来说,读取的一个数据版本的trx_id,有以下几种可能:

  • 如果被访问版本的trx_id属性值与ReadView中的 creator_trx_id 值相同,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。

  • 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;

  • 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;

  • 如果落在黄色部分,那就包括两种情况

  • 若 数据的trx_id在trx_ids数组中,表示这个版本是由还没提交的事务生成的,不可见, 去读取这条数据的历史版本,这条数据的历史版本中都包含了事务id信息,去查找第一个不在活跃事务数组的版本记录。 若 数据的trx_id不在trx_ids数组中,表示这个版本是已经提交了的事务生成的,可见。

这种通过版本链 + 一致性视图 来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制),现在你明白MySQL如何实现了“秒级创建快照”的能力了吧。

还是不懂?举例说明

如果你对MVCC机制的整个流程还是比较模糊,我们现在举例来说明下。

比如student表中有一个事务id为8的插入记录:

insert into student(id, name, class) values(1, '张三', '一班') 复制代码

我们现在在MySQL的读已提交和可重复读隔离级别下,MVCC机制的整个工作流程。

MySQL中的读未提交和序列化并不需要MVCC机制,读未提交,直接读取别人未提交的数据,而序列化全程用加锁的方式,也用不上MVCC, 大家体会下。

可重复读隔离级别下

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

begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。

事务10

事务20

事务30

beginUPDATE student SET name="李四" WHERE id=1;UPDATE student SET name="王五" WHERE id=1;

begin更新了一些其他表的数据

beginSELECT * FROM student WHERE id = 1;

​事务10和20均为提交,现在事务30执行select, 那么得到的结果是什么呢?

  1. 在执行select语句时会先生成一个ReadView,ReadView的trx_ids列表的内容就是[10, 20],min_trx_id为10,max_trx_id为21,creator_trx_id为0。

  2. 然后从版本链中挑选可见的记录,从图中看出,最新版本的列name的内容是'王五',该版本的trx_id值为10,在trx_ids列表内,所以不符合可见性要求,根据roll_pointer跳到下一个版本。

  3. 下一个版本的列name的内容是'李四',该版本的trx_id值也为10,也在trx_ids列表内,所以也不符合要求,继续跳到下一个版本。

  4. 下一个版本的列name的内容是'张三',该版本的trx_id值为8,小于ReadView中的min_trx_id值10,说明已经提交了,那么最终返回'张三'。

读已提交隔离级别下

读已提交READ COMMITTED是每次读取数据前都生成一个ReadView。基本的规则和流程与可重复读隔离级别一致,这里不做重复赘叙。

总结

本问重点介绍了MVCC机制,以及 MVCC 在 READ COMMITTD、 REPEATABLE READ 这两种隔离级别的事务在执行快照读操作时访问记录的版本链的过程。这样使不同事务的 读-写 、 写-读 操作并发执行,从而提升系统性能。

  • READ COMMITTD 在每一次进行普通SELECT操作前都会生成一个ReadView

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

如果本文对你有帮助的话,请留下一个赞吧

MySQL的MVCC机制看完这篇你还不懂,算我输相关推荐

  1. python装饰器原理-看完这篇文章还不懂Python装饰器?

    原标题:看完这篇文章还不懂Python装饰器? 1.必备 2.需求来了 初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作.redis调用.监控API等功能.业务部门 ...

  2. 大写的服,看完这篇你还不懂RocketMQ算我输

    目录 RocketMQ介绍 RocketMQ概念 为什么要用RocketMQ? 异步解耦 削峰填谷 分布式事务最终一致性 数据分发 RocketMQ架构 RocketMQ消息类型 普通消息 顺序消息 ...

  3. mq幂等mysql_膜拜!看完这篇你还不懂RocketMQ算我输

    RocketMQ 介绍 Apache RocketMQ 是一款 低延迟.高并发.高可用.高可靠的分布式消息中间件.消息队列 RocketMQ 可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备 ...

  4. 收藏!最详细的Python全栈开发指南 看完这篇你还不会Python全栈开发 你来打我!!!

    Python Web全栈开发入门实战教程教程    大家好,我叫亓官劼(qí guān jié ),这个<Python Web全栈开发入门实战教程教程>是一个零基础的实战教程,手把手带你开 ...

  5. 我就不信看完这篇你还搞不懂信息熵

    我就不信看完这篇你还搞不懂信息熵 https://mp.weixin.qq.com/s/7NrB0UtmELXD3UNO3C6jGA 让我们说人话!好的数学概念都应该是通俗易懂的. 信息熵,信息熵,怎 ...

  6. 看完这篇你还敢说分不清 Java 类 对象 实例 变量间的区别?

    看完这篇你还敢说分不清 Java 类 对象 实例 变量间的区别? 什么是类? 什么是对象? 什么是变量? 对象和类的关系: Java中的类: Java中的对象 Java中的变量 Java 中调用 对象 ...

  7. 你不知道的js中关于this绑定机制的解析[看完还不懂算我输]

    前言 最近正在看<你不知道的JavaScript>,里面关于this绑定机制的部分讲的特别好,很清晰,这部分对我们js的使用也是相当关键的,并且这也是一个面试的高频考点,所以整理一篇文章分 ...

  8. 程序异常异常代码: 0xc0000005_Java基础:看完这篇你还怕碰到异常吗?

    前言 在日常的开发以及平时的学习练习中,异常相信对于大家来讲并不陌生,但是对于异常的具体使用.底层实现以及分类等等可能并不是很了解.今天我就抽出了一点时间系统的整理了异常的各个知识点,希望能够帮助到大 ...

  9. logback property 默认值_看完这篇文章还不会给spring boot配置logback,请你吃瓜

    每一个成功人士的背后,必定曾经做出过勇敢而又孤独的决定. 放弃不难,但坚持很酷~ 一.logback日志框架 logback 是一个开源的日志组件,由三个部分组成:logback-core,logba ...

最新文章

  1. mysql开启skip-name-resolve 导致root@127.0.0.1(localhost)访问引发的ERROR 1045 (28000)错误解决方案...
  2. 周志华 机器学习 笔记
  3. 基于OHCI的USB主机 —— 寄存器(其它)
  4. GIF图片合集(用于网络请求图片用)
  5. 25. K 个一组翻转链表
  6. typora设置代码不自动换行
  7. G-TAD: Sub-Graph Localization for Temporal Action Detection
  8. PostgreSQL的中文拼音排序
  9. 逆序数介绍以及算法实现浅析
  10. mysql修改指定记录_sql操作之修改记录值
  11. js文字展示各种滚动效果
  12. shell 脚本中常用的列表
  13. Android 录屏+录音,原生代码,无需root权限,好用更好懂
  14. vga焊接线顺序_vga线序是怎么排列的?
  15. java 成员变量存在哪_Java中成员变量、局部变量、全局变量、静态变量存在位置及初始化...
  16. 7z怎么解压linux,7z 常用解压命令
  17. 全国计算机等级考试c语言编程题,全国计算机等级考试C语言编程题(附答案)
  18. valid/ready握手协议之ready打拍
  19. 机器学习实战-预测数值型数据:回归
  20. 初识微信小程序 图片与声音

热门文章

  1. 使用Visual Studio 2010调试断点不起作用的问题解决办法
  2. picacg本地缓存目录_picacg保存路径 | 手游网游页游攻略大全
  3. 文案把卖点被埋没?如此挖掘电商产品卖点,让你轻松获客
  4. 干货!11个产品营销策略(一)
  5. FlexNetLicensing简介(由FlexNetPublisher和FlexNetEmbedded构成)
  6. 计算机右侧不显示桌面,电脑桌面的右下角不显示移动设备的图标怎么办?
  7. 楚翔教师网上学习助手桌面版
  8. 机器学习中常见的损失函数和代价函数
  9. 复试口语(二)自我介绍
  10. python 货币规范化_货币在Python中的格式