MySQL MVCC机制探秘:数据一致性与并发处理的完美结合,助你成为数据库高手


一、前言

在分析 MVCC 的原理之前,我们先回顾一下 MySQL 的一些内容以及关于 MVCC 的一些简单介绍。(注:下面没有特别说明默认 MySQL 的引擎为 InnoDB )

1.1 数据库的并发场景

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

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

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

1.2 什么是 MVCC

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

一句话概述,MVCC 在 MySQL InnoDB 中的实现主要是为了提高数据库并发性能,用更好的方式去处理读写冲突,做到即使有读写冲突时,也能做到不加锁,做到非阻塞并发读

1.3 当前读和快照读

  • 当前读

    select xxx lock in share mode; # 共享锁

    #排它锁
    select xxx for update;
    update xxx;
    delete xxx;
    insert xxx;

    像上面的这些操作就是一种当前读,因为它读取的是数据的最新版本,读取时还要保证其他事务不能修改当前记录,会对记录进行加锁。

  • 快照读

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

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

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

1.5 MVCC 能解决什么问题

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

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

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

  1. MVCC + 悲观锁

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

  1. MVCC + 乐观锁

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

二、MVCC实现原理

2.1 隐式字段

在一张表中,除了我们自定义的字段,实际上 MySQL 会隐式的定义一些字段。

  • DB_TRX_ID

    6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID

  • DB_ROLL_PTR

    7byte,回滚指针,指向这条记录的上一个版本(存储于 rollback segment 里)

  • DB_ROW_ID

    6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引

  • 实际还有一个删除 flag 隐藏字段, 即记录被更新或删除并不代表真的删除,而是删除 flag 变了

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

59c8504fac0e965a699bd647b86a2075.jpeg

2.2 undo log日志

undo log 类型

  • insert undo log

    是指在 insert 操作中产生的 undo log。因为 insert 操作的记录,只对当前事务本身可见,对其他事务不可见(这是事务隔离性的要求),因此这种 undo log 可以在事务提交后直接删除。不需要进行 purge 操作

  • update undo log

    是对 delete 和 update 操作产生的 undo log。该 undo log 可能需要提供给 MVCC 机制使用,因此不能在事务提交时就进行删除,提交时放入 undo log 链表,等待 purge 线程进行最后的删除。

 

purge 线程

为了实现 InnoDB 的 MVCC 机制,更新或者删除操作都只是设置一下老记录的 deleted_bit(即前面提到的删除 flag ),并不真正将过时的记录删除。为了节省磁盘空间,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 log ,undo log 存在于 rollback segment 中旧记录链,它的执行流程如下:

  1. 一个事务插入 person 表插入了一条新记录,记录如下,name 为 Jerry, age 为24,隐式主键是1,事务ID和回滚指针我们假设为NULL。
c6833cc0c414d33f0a96a06ae82ec1aa.jpeg

2.现在来了一个 事务1 对记录的 name 进行了修改,改为了 Tom,执行过程如下:

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

3.又来了个事务2修改 person 表的同一个记录,将 age 修改为30岁,执行过程如下:

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

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

2.3 Read View

 

什么是 Read View ?

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

 

可见行判断

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

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

 

判断条件是什么?

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

  • trx_list 一个数值列表,用来维护 Read View 生成时刻此时系统正活跃的事务ID

  • up_limit_id 记录 trx_list 中的最小的事务 ID

  • low_limit_id 在 Read View 生成时刻系统尚未分配的下一个事务 ID,即目前(不一定是 Read View 中)已经出现过的事务 ID 最大值+1

     

    比较步骤

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

2.4 实现流程

我们在了解了隐式字段、undo log 以及 Read View 的概念之后,我们模拟一下 MVCC 实现的整体流程。

  1. 假设当前有四个事务,当事务2对某行数据执行了快照读,数据库为该行数据生成一个 Read View 读视图,假设当前事务ID为2,此时还有事务1和事务3在活跃中,事务4在事务2快照读前一刻提交更新了,所以 Read View 记录了系统当前活跃事务1,3的ID,维护在一个列表上,假设我们称为 trx_list 。





    事务1事务2事务3事务4事务开始事务开始事务开始事务开始………修改且已提交进行中快照读进行中
    ………
  2. 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 如下图

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

    0d06227f939fe73269d74daeb403e0bc.jpeg
  4. 所以先拿该记录 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提交的版本也是全局角度上最新的版本。

&nbsp;

流程图

1548a3bf6cf883b437b78543175d9aca.jpeg

2.5 RC/RR级别快照读有什么不同

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

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

总之在 RC 隔离级别下,每次快照读都会生成最新的 ReadView;而在 RR 级别下,则是同一个事务中的第一个快照读才会创建ReadView,之后的快照读获取的都是同一个 ReadView。所以说 RR 在 RC 的基础上通过生成 Read View 的时机不同从而解决了不可重复读的问题

总结

本文讲解了 MySQL 中的隐式字段、undo log 日志和 Read View 的原理以及 MVCC 的实现流程,对于我们在日常的开发过程中对于数据库的并发操作以及 MySQL 的各种隔离级别有了清晰的认识。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/116501.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

竞赛选题 深度学习人脸表情识别算法 - opencv python 机器视觉

文章目录 0 前言1 技术介绍1.1 技术概括1.2 目前表情识别实现技术 2 实现效果3 深度学习表情识别实现过程3.1 网络架构3.2 数据3.3 实现流程3.4 部分实现代码 4 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习人脸表情识别系…

《java核心卷Ⅰ》知识点总结(可作面试题)

&#x1f6eb; JDK和JRE傻傻分不清?&#x1f6eb; HelloWorld的输出都经历了啥&#xff1f;&#x1f6eb; Java的三个版本都是啥&#xff1f;&#x1f6eb; 关于main方法你都知道啥&#xff1f;main方法被声明为private会怎样&#xff1f;&#x1f6eb; 强制and自动类型转换都…

8.1 矢量图层符号化整体说明

文章目录 前言符号化与图层渲染符号符号层渲染器 总结 前言 地图制图是QGIS的优势所在&#xff0c;QGIS包含完整的地图制图功能&#xff0c;如标注与注记、符号化与地图综合等多种功能。 符号化&#xff08;Symbolization&#xff09;是指利用符号将地理事物或现象抽象化。 QGi…

小知识(5) el-table行样式失效问题

一、实现效果 子级呈现不同颜色去区分 二、最初代码 tips: 我这里使用的vue3 elementplus <el-table :row-class-name"tableRowClassName" >... </el-table>function tableRowClassName({ row, rowIndex }) {if (row.children.length 0) {return …

Django 地址接口开发

应用 Mixin 混合类进行收货地址接口开发 python ../manage.py startapp address继承了mixins扩展类&#xff0c;进到里面可以稍微看下源码 该方法帮我们实现了获取验证及保存的功能 address/views from rest_framework.generics import GenericAPIView from rest_framewo…

移动端滚动穿透与滚动溢出解决方案

滚动穿透 问题描述 在移动端 WEB 开发的时候&#xff08;小程序也雷同&#xff09;&#xff0c;如上录屏所示&#xff0c;如果页面超过一屏高度出现滚动条时&#xff0c;在 fixed 定位的弹窗遮罩层上进行滑动&#xff0c;它下面的内容也会跟着一起滚动&#xff0c;看起来好像事…

PC连wifi,网线连接旭日X3派以共享网络

PC电脑连好wifi&#xff0c;找到【控制面板->网络和Internet->网络和共享中心->查看网络状态和任务->更改适配器设置】 找到WLAN&#xff0c;右键【属性->共享】勾上允许&#xff0c;然后【确定】。 现在去与PC通过网线连接好的X3派上配置&#xff1a; 参考&a…

SAP MM学习笔记38 - 入库/请求自动决济(ERS - Evaluated Receipt Settlement)

之前的章节学习了请求书的方方面面&#xff0c;这一章来个终章&#xff0c;入库/请求自动决济&#xff1a;&#xff09;。 1&#xff0c;什么是 ERS ERS&#xff0c;即 入库/请求自动决济&#xff0c;是 自動決済&#xff08;Automatic Settlement&#xff09;功能的一种。 以…

python之Cp、Cpk、Pp、Ppk

目录 1、Cp、Cpk、Pp、Ppk 2、python计算 1、Cp、Cpk、Pp、Ppk Cp Process Capability Ratio 可被译为“过程能力指数” Cpk Process Capability K Ratio 可被译为“过程能力K指数” Pp Process Performance Ratio 可被译为“过程绩效指数” Ppk Process Performance K Ra…

我的创作纪念日 - 第四年

机缘 几乎自己的所有文章都用同一个模板&#xff0c;虽然高效&#xff0c;但也乏味&#xff0c;就让每年都有一次例外吧。 不知不觉已经过去了四年了&#xff0c;虽然很早就在CSDN查阅资料&#xff0c;但是真正落笔创作是在4年前。那个时候自己已经是一个从事培训讲师&#x…

ETL实现实时文件监听

一、实时文件监听的作用及应用场景 实时文件监听是一种监测指定目录下的文件变化的技术&#xff0c;当产生新文件或者文件被修改时&#xff0c;可实时提醒用户并进行相应处理。这种技术广泛应用于数据备份、日志管理、文件同步和版本控制等场景&#xff0c;它可以帮助用户及时…

桥梁结构健康监测系统落地方案

桥梁结构健康监测的意义是多方面的。首先&#xff0c;它可以实时采集桥梁的结构数据&#xff0c;并对其进行处理和分析&#xff0c;以确定结构损伤的位置、评估桥梁的健康状况&#xff0c;并预测承载力的发展趋势。这有助于及时发现桥梁的结构问题和潜在风险&#xff0c;为采取…

JAVA入门总结回顾

1.常用的DOS命令&#xff1a;DOS窗口常用命令-CSDN博客 2.检查jdk是否安装成功&#xff1a;在cmd中输入java -version或者java或者javac。出现相应的对应显示内容。 3.JDK&#xff0c;JRE之间的关系&#xff1a;JDK是JAVA的开发工具包&#xff0c;JRE是JAVA的的运行环境。JRE…

rabbitMQ(3)

RabbitMq 交换机 文章目录 1. 交换机的介绍2. 交换机的类型3. 临时队列4. 绑定 (bindings)5. 扇形交换机&#xff08;Fanout &#xff09; 演示6. 直接交换机 Direct exchange6.1 多重绑定6.2 direct 代码案例 7. 主题交换机7.1 Topic 匹配案例7.2 Topic 代码案例 8. headers 头…

Kubernetes-进阶(Pod生命周期/调度/控制器,Ingress代理,数据存储PV/PVC)

Kubernetes-进阶 Pod详解 每个Pod中都可以包含一个或多个容器&#xff0c;这些容器可以分两类 用户程序所在容器&#xff0c;数量用户决定Pause容器&#xff0c;这是每个Pod都会有的一个根容器&#xff0c;它的作用有两个 可以以它为依据&#xff0c;评估整个Pod的健康状态可以…

itbuilder软件在线设计数据库模型,AI与数据库擦出的火花

今天要介绍一款强大的软件&#xff0c;它就是itBuilder软件&#xff0c;一款在线设计数据库模型软件&#xff0c;借助人工智能提高效率&#xff0c;可以生成CRUD代码并推送至开发工具中&#xff1b;它涵盖了几乎所有语言&#xff0c;如Java、Python、JavaScript等&#xff0c;并…

4种实用的制作URL 文件的方法

很多小伙伴有自己的博客、淘宝或者共享文件网站&#xff0c;想要分享、推广自己的网址做成url文件&#xff0c;让别人点击这个url文件直接访问自己的网站。URL文件其实就一个超级链接&#xff0c;制作的方法很多&#xff0c;这里列举4种。 收藏网站直接拖拽 1.第一种&#xf…

drf-过滤、排序、异常处理、自封装Response

过滤 过滤就是根据路由url?后的信息过滤出符合&#xff1f;后条件的数据而非全部&#xff0c;比如…/?nameweer就是只查name是weer的数据&#xff0c;其余不返回。 1、安装&#xff1a;pip3 install django-filter2、注册&#xff1a;在settings.py中的app中注册django-filt…

服务器数据恢复-某银行服务器硬盘数据恢复案例

服务器故障&分析&#xff1a; 某银行的某一业务模块崩溃&#xff0c;无法正常使用。排查服务器故障&#xff0c;发现运行该业务模块的服务器中多块硬盘离线&#xff0c;导致上层应用崩溃。 故障服务器内多块硬盘掉线&#xff0c;硬盘掉线数量超过服务器raid阵列冗余级别所允…

【T3】畅捷通T3备份账套提示:超时已过期,错误‘53‘文件不存在。

【问题描述】 针对畅捷通T3软件&#xff0c;进行账套备份&#xff08;账套输出&#xff09;的时候&#xff0c; 先是提示”超时已过期“&#xff1b; 点击确定后&#xff0c;再次提示&#xff1a;运行时错误53&#xff0c;文件未找到。 最终导致账套备份/输出失败。 【解决…