1.前导知识

1.1什么是MVCC

  • MVCC全称 Multi-Version Concurrency Control,即多版本并发控制,MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
  • 多版本控制:指的是一种提高并发的技术,最早的数据库系统,只有读读之间可以并发,读写、写读、写写之间都要阻塞,引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅的提高了InnoDB的并发度,在内部实现中,InnoDB是通过undo log实现的,通过undo log可以找回数据的历史版本。找回的历史版本可以提供给用户读(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也可以在回滚的时候覆盖数据页上的数据。在InnoDB内部中,会记录一个全局的活跃读写事务数组,其主要用来判断事务的可见行。
  • 一句话概述MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理读-写冲突,做到即使有读写冲突时,也能做到不加锁,做到非阻塞并发读

1.2当前读与快照读

  • 当前读

    像select lock in share mode(共享锁); select for update, update,delete,insert(排它锁)这些操作就是一种当前读,因为它读取的是数据的最新版本,读取时还要保证其他事务不能修改当前记录,会对记录进行加锁

  • 快照读

    不加锁的select就是快照读,即不加锁的非阻塞读;(快照读的前提是隔离级别不是串行化,串行化的隔离级别下快照读会退化成当前读) 之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但是它在很多情况下避免了加锁操作,降低了开销,既然是基于多版本,所以快照读可能读到的不一定是数据的最新版本,而有可能是之前的历史版本。

说白了MVCC就是为了实现读-写不冲突,而这个读指的就是快照读,而非当前读,当前读实际上是一种加锁的操纵,是悲观锁的实现

1.3当前读和快照读与MVCC的关系

  • 准确的说,MVCC多版本并发控制指的是 “维护一个数据的多个版本,使得读写操作没有冲突” 这么一个概念,仅仅是一个理想状态
  • 而在MySQL中,实现这么一个MVCC理想概念,我们就需要MySQL提供具体的功能去实现,而快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能,而相对而言,当前读就是一个悲观锁的具体功能实现
  • 而要说的在细致一点,快照读本身也是一个抽象概念,在深入研究,MVCC模型在MySQL中的具体实现则是由三个隐式字段undo logRead View等去完成的。

1.4MVCC能解决什么问题

数据库并发场景有三种,分别是:

  • 读-读: 不存在任何问题,不需要并发控制
  • 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读、不可重复读、幻读等问题。
  • 写-写:有线程安全问题,可能会存在更新丢失的问题,比如第一类更新丢失,第二类更新丢失。

(第一类丢失更新:事务A回滚时,将已经提交的事务B的更新数据覆盖了;第二类丢失更新:事务A提交覆盖了事务B已经提交的数据,造成事务B所做的操作丢失)

MVCC带来的好处

MVCC是一种解决写-读冲突的无锁并发控制手段,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决一下问题:

  • 在并发读数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写性能
  • 同时还可以解决脏读、不可重复读、幻读等事务隔离问题,但不能解决更新丢失的问题。事务总结

总结:MVCC就是开发人员不满意只让数据库采用悲观锁(加锁)这样性能不佳的形式去解决读-写的问题,而提出的解决方案,所以在数据库中,因为有了MVCC,所以我们可以形成两个组和

  • MVCC + 悲观锁

    MVCC解决写-读冲突,悲观锁解决写写-冲突

  • MVCC + 乐观锁

    MVCC解决读写冲突,乐观锁解决写写冲突。

2.MVCC实现原理

2.1隐式字段

在一张表中,除了我们自定义的列,实际上MySQL会隐式的定义DB_TRX_ID(最后一次修改该记录的事务ID), DB_ROLL_PTR(指向这条记录的上一个版本), DB_ROW_ID(隐藏主键)等字段。

  • DB_TRX_ID
    6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR
    7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
  • DB_ROW_ID
    6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
  • 实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了

如上图,DB_ROW_ID是数据库为改行记录生产的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个版本。

2.2undo log日志

undo log主要被分为两种:

  • insert undo log

    代表事务在insert新纪录时产生的undo_log,只在事务回滚时需要,并且在事务提交后可以被立即抛弃。

  • update undo log

    事务在update或者delete时产生的undo log,不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只要在快速读或者事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除。

purge线程

  • 从前面的分析可以看出,为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下老记录的deleted_bit,并不真正将过时的记录删除。
  • 为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作,purge线程自己也维护了一个read view(这个read view相当于系统中最老活跃事务的read view);如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。

对MVCC有帮助的实质是update undo logundo log实际上就是存在rollback segment中旧记录链,它的执行流程如下:

一、 比如一个有个事务插入persion表插入了一条新记录,记录如下,name为Jerry, age为24岁,隐式主键是1,事务ID回滚指针,我们假设为NULL

二、 现在来了一个事务1对记录的name进行了修改,改为了tom

  • 事务1修改该行数据时,数据会先对这行记录加排它锁
  • 然后把改行数据拷贝到undo log中,作为旧记录,即在undo log中由当前行的拷贝副本
  • 拷贝完毕后,修改该行的name为tom,并且修改隐藏字段的事务ID为当前事务1的ID,我们默认从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,即表示我的上一个版本就是它
  • 事务提交后,释放排它锁

三、 又来了个事务2修改person表的同一个记录,将age修改为30岁

  • 事务2修改该行数据时,数据库也先为该行加锁
  • 然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
  • 修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
  • 事务提交,释放锁

从上面我们可以看出,不同事务或者相同事务对同一记录的修改,会导致该记录的undo log称为一条记录版本线性表,即链表,undo log的表头就是最新的旧记录,(当然就像之前说的该undo log的节点可能是会purge线程清除掉,像图中的第一条 insert undo log,其实在事务提交之后可能就被删除丢失了,不过这里为了演示,所以还放在这里)。

2.3Read View

什么是Read View?

RV就是事务进行快照读操作时产生的读视图(RV),在该事务执行快照读的那一刻,会生成数据库系统的当前的一个快照,记录并维护当前活跃事务的ID(当每个事务开启时,都会被分配一个ID,这个ID是自增的,所以最新的事务,ID值越大)

大致流程

所以我们知道RV主要是用来做可见行判断的,即当我们某个事务执行快照读的时候,对该记录创建一个RV读视图,把它比作条件来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据

RV遵循一个可见性算法,主要是将要被修改的数据的最新记录的DB_TRX_ID(即当前事务ID),与系统当前其他活跃事务的ID去对比(由RV维护),如果DB_TRX_ID跟RV的属性做了某些对比,不符合可见性,那么就由DB_ROLL_PTR回滚指针去取出undo log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链表头到尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID,那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本

判断条件是什么?

我们可以将RV简单的理解为三个全局属性

1. trx_list 一个数值列表,用来维护RV生成时刻此时系统正活跃的事务ID
2. up_limit_id 记录trx_list中的最小的事务ID
3. low_limit_id  RV生成时刻系统尚未分配的下一个事务ID,即目前(不一定是RV中)已经出现过的事务ID最大值+1

比较步骤

  • 首先比较DB_TRX_ID < up_limit_id, 如果小于,则当前事务能看到DB_TRX_ID 所在的记录,如果大于等于进入下一个判断
  • 接下来判断 DB_TRX_ID 大于等于 low_limit_id , 如果大于等于则代表DB_TRX_ID 所在的记录在Read View生成后才出现的,那对当前事务肯定不可见,如果小于则进入下一个判断
  • 判断DB_TRX_ID 是否在活跃事务之中,trx_list.contains(DB_TRX_ID),如果在,则代表我Read View生成时刻,你这个事务还在活跃,还没有Commit,你修改的数据,我当前事务也是看不见的;如果不在,则说明,你这个事务在Read View生成之前就已经Commit了,你修改的结果,我当前事务是能看见的

2.4实现流程

MVCC总体实现流程

我们在了解了隐式字段undo log, 以及Read View的概念之后,就可以来看看MVCC实现的整体流程是怎么样了

整体的流程是怎么样的呢?我们可以模拟一下

  • 事务2对某行数据执行了快照读,数据库为该行数据生成一个Read View读视图,假设当前事务ID为2,此时还有事务1事务3在活跃中,事务4事务2快照读前一刻提交更新了,所以Read View记录了系统当前活跃事务1,3的ID,维护在一个列表上,假设我们称为trx_list
事务1 事务2 事务3 事务4
事务开始 事务开始 事务开始 事务开始
修改且已提交
进行中 快照读 进行中
  • Read View不仅仅会通过一个列表trx_list来维护事务2执行快照读那刻系统正活跃的事务ID,还会有两个属性up_limit_id(记录trx_list列表中事务ID最小的ID),low_limit_id(记录trx_list列表中事务ID最大的ID,也有人说快照读那刻系统尚未分配的下一个事务ID也就是目前已出现过的事务ID的最大值+1,所以在这里例子中up_limit_id就是1,low_limit_id就是4 + 1 = 5,trx_list集合的值是1,3,Read View如下图

  • 我们的例子中,只有事务4修改过该行记录,并在事务2执行快照读前,就提交了事务,所以当前该行当前数据的undo log如下图所示;我们的事务2在快照读该行记录的时候,就会拿该行记录的DB_TRX_ID去跟up_limit_id,low_limit_id活跃事务ID列表(trx_list)进行比较,判断当前事务2能看到该记录的版本是哪个。

  • 所以先拿该记录DB_TRX_ID字段记录的事务ID 4去跟Read View的的up_limit_id比较,看4是否小于up_limit_id(1),所以不符合条件,继续判断 4 是否大于等于 low_limit_id(5),也不符合条件,最后判断4是否处于trx_list中的活跃事务, 最后发现事务ID为4的事务不在当前活跃事务列表中, 符合可见性条件,所以事务4修改后提交的最新结果对事务2快照读时是可见的,所以事务2能读到的最新数据记录是事务4所提交的版本,而事务4提交的版本也是全局角度上最新的版本

MVCC工作流程

3.MVCC相关问题

3.1RR是如何在RC的基础上解决不可重复读的?

表1

事务A 事务B
开启事务 开启事务
快照读(无影响)查询金额为500 快照读查询金额为500
更新金额为400
提交事务
select 快照读金额为500
select lock in share mode当前读金额为400

在上表的顺序下,事务B的在事务A提交修改后的快照读是旧版本数据,而当前读是实时新数据400

表2

事务A 事务B
开启事务 开启事务
快照读(无影响)查询金额为500
更新金额为400
提交事务
select 快照读金额为400
select lock in share mode当前读金额为400

而在表2这里的顺序中,事务B在事务A提交后的快照读和当前读都是实时的新数据400,这是为什么呢?

  • 这里与上表的唯一区别仅仅是表1的事务B在事务A修改金额前快照读过一次金额数据,而表2的事务B在事务A修改金额前没有进行过快照读。

所以我们知道事务中快照读的结果是非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方非常关键,它有决定该事务后续快照读结果的能力,我们这里测试的是更新,同时删除更新也是一样的,如果事务B的快照读是在事务A操作之后进行的,事务B的快照读也是能读取到最新的数据的

3.2RC/RR级别下的InnoDB快照读有什么不同

正是由于生成Read View的时机不同,从而造成RC RR级别下快照读的结果的不同。

  • 在RR级别下的某个事务对某条记录进行的第一次快照读会创建一个快照Read View,此后在调用快照读的时候,使用的还是同一个ReadView,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见.
  • 而在RC隔离级别下,事务中每次快照都会生成一个快照和ReadView,这就是我们在RC级别下的事务中可以额看到别的事务提交更新的原因。

总之在RC隔离级别下,每次快照读都会生成最新的ReadView;而在RR级别下,则是同一个事务中的第一个快照读才会创建ReadView,之后的快照读获取的都是同一个ReadView

3.MySQL如何解决幻读与不可重复读

参考

MySQL之MVCC原理详解相关推荐

  1. MySQL 的 MVCC 原理详解

    什么是 MVCC ? mvcc,全称 multi-version concurrency control,即多版本并发控制 mvcc 是 mysql 的 innodb 引擎为了解决读写冲突,通过不加锁 ...

  2. mysql mvcc 原理详解

    前言 很多人在谈起mysql事务的时候都能很快的答出mysql的几种事务隔离级别,以及在各自隔离级别下产生的问题,但是一旦谈到为什么会产生这样的结果时会觉得难以回答,说到底,还是对底层的原理未做深入的 ...

  3. oracle的mvcc解析,PostgreSQL原理:Oracle 和 MySQL 中MVCC机制详解

    MVCC,Multi-version Concurrency Control ,顾名思义指的是多版本并发控制.在介绍MVCC之前我们先来简单了解下事务的隔离级别: read uncommitted:脏 ...

  4. MySQL的crash-safe原理详解

    MySQL作为当下最流行的开源关系型数据库,有一个很关键和基本的能力,就是必须能够保证数据不会丢.那么在这个能力背后,MySQL是如何设计才能保证不管在什么时间崩溃,恢复后都能保证数据不会丢呢?有哪些 ...

  5. MySQL事务实现原理详解

    1.事务概述 事务的详细概述 什么是事务? 事务时是访问和更新数据的程序执行单元,事务中可能含有一个或多个SQL语句,这些语句要么全部执行,要么都不执行 回顾MySQL的逻辑架构与存储引擎 如上图所示 ...

  6. MySQL底层执行原理详解

    一.MySQL的内部组件结构 大体来说,MySQL 可以分为 Server 层和存储引擎层两部分. 1.Server层 ​ 主要包括连接器.查询缓存.分析器.优化器.执行器等,涵盖 MySQL 的大多 ...

  7. 看一遍就理解:MVCC原理详解

    前言 MVCC实现原理是一道非常高频的面试题,最近技术讨论群的小伙伴一直在讨论,趁着国庆节有空,我们一起来聊聊. 1. 相关数据库知识点回顾 1.1 什么是数据库事务,为什么要有事务 事务,由一个有限 ...

  8. MySQL 的主从复制原理详解高级

    首先要明白为什么要用 mysql 的主从复制: 1–在从服务器可以执行查询工作 (即我们常说的读功能),降低主服务器压力:(主库写,从库读,降压) 2–在从主服务器进行备份,避免备份期间影响主服务器服 ...

  9. mysql的MVCC机制详解

    文章目录 1. 什么是MVCC机制? 2. MVCC机制底层是怎么实现的 2.1 undo日志版本链 2.2 read view机制 3. undo版本链和read-view的对比规则 4. 结合案例 ...

最新文章

  1. 做技术的,年龄大了你慌吗?
  2. java工程中的.classpathaaaaaaaaaaaaaaaa转载
  3. Windows Phone开发手记-WinRT下分组拼音的实现
  4. printf按8进制、16进制输出
  5. table: github.com/astaxie/beego/orm.Ormer not found
  6. 一、在windows环境下修改pip镜像源的方法(以python3为例)
  7. backtype.storm.generated.InvalidTopologyException:null问题的解决
  8. 数据驱动工程:跟踪使用,合理决策
  9. keil窗口显示不全_使用Keil语言的嵌入式C编程教程(下)
  10. JVM性能调优(一)(JVM参数详解、内存分析等)
  11. 「BJOI 2019」勘破神机
  12. java学习个人总结_Java学习的总结
  13. 我自己的java软件开发职业规划
  14. 干货!#黑客大会PPT资源#,快来领取吧!
  15. 万代南梦宫假面骑士时尚品牌HENSHIN by KAMEN RIDER首登中国;安宏资本宣布与资生堂达成协议 | 知消...
  16. python批量建立文件夹
  17. N32926 usb gadget 加 adb驱动, 插入USB线一点反应都没有。
  18. 无线传感网络的基本结构
  19. 蓝汛ChinaCache打破传输瓶颈,提高宽带利用率
  20. 关于移动互联网的新要求

热门文章

  1. 《AI人工智能的未来》(Yanlz+VR云游戏+Unity+SteamVR+云技术+5G+AI+机器学习+人机交互+计算机视觉+生物特征识别+立钻哥哥+==)
  2. 寄存器、RAM、ROM、Flash
  3. js map循环与find循环
  4. jenkins安装maven及配置
  5. 使用Spark+Hanlp进行简单的文本处理(中)-Kmeans文本聚类
  6. 期末计算机课总结100字,简短教师工作总结100字4篇
  7. iOS原生App与H5页面交互 离线缓存 笔记
  8. php保留小数位数_php number_format()保留小数点后几位
  9. 轻松清理Windows电脑垃圾文件,提升优化电脑速度
  10. RabbitMq 创建用户