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.rhkb.cn/news/169003.html

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

相关文章

基于springboot实现广场舞团平台系统项目【项目源码+论文说明】计算机毕业设计

基于SPRINGBOOT实现广场舞团平台系统演示 摘要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&am…

算法通关村第十一关青铜挑战——移位运算详解

大家好&#xff0c;我是怒码少年小码。 计算机到底是怎么处理数字的&#xff1f; 数字在计算机中的表示 机器数 一个数在计算机中的二进制表示形式&#xff0c;叫做这个数的机器数。 机器数是带符号的&#xff0c;在计算机用一个数的最高位存放符号&#xff0c;正数为0&am…

Unity之ShaderGraph如何实现全息投影效果

前言 今天我们来实现一个全息投影的效果&#xff0c;如下所示&#xff1a; 主要节点 Position&#xff1a;提供对网格顶点或片段的Position 的访问&#xff0c;具体取决于节点所属图形部分的有效着色器阶段。使用Space下拉参数选择输出值的坐标空间。 Time&#xff1a;提…

C++入门(3):引用,内联函数

一、引用 1.1 引用特性 引用必须初始化 一个变量可以有多个引用 引用一旦引用一个实体&#xff0c;就不能引用其他实体 int main() {int a 10, C 20;int& b a;b c; // 赋值&#xff1f;还是b变成c的别名&#xff1f;return 0; }1.2 常引用 引用权限可以平移或缩小…

ubuntu双系统安装以及启动时卡死解决办法

目录 一.简介 二.安装 如何安装Ubuntu20.04(详细图文教程-CSDN博客 Ubuntu22.04&#xff08;非虚拟机&#xff09;安装教程&#xff08;2023最新最详细&#xff09;-CSDN博客 三.ubuntu双系统启动时卡死解决办法&#xff08;在ubuntu16.04和18.04测试无误&#xff09; 问题…

vue实现响应式改变scss样式

需求&#xff1a;侧边导航栏点击收起&#xff0c;再次点击展开&#xff0c;但是我这个项目的位置是在左侧菜单栏所以需要自定义 效果图&#xff1a; 实现步骤&#xff1a; 1&#xff1a;定义一个变量&#xff08;因为我这里会存储菜单栏的状态所以需要存储状态&#xff0c;一…

09、Python 字典入门 及 高级用法

目录 字典创建字典通过key访问value添加key-value对删除key-value对替换key-value对 判断是否包含指定keydict与列表字典的常用方法演示&#xff1a; 用字典格式化字符串 创建字典 操作字典key-value对 理解dict与list的关系 字典常用方法 使用字典格式化字符串 字典 字典用于…

2023/10/23 mysql学习

数据库修改 show databases; 展示所有数据库 create database 数据库名; 创建数据库 create database if not exists 数据库名; 如果未创建过当前数据库名则创建 drop database 数据库名; drop database if exists 数据库名;用法和创建类似 删除数据库 use 数据库名; 跳…

分享一下商城小程序怎么设置分销功能

随着互联网的快速发展&#xff0c;传统的营销方式已经无法满足企业的需求。在这个时代&#xff0c;拥有一个高效的分销系统已经成为了企业成功的关键之一。而商城小程序作为近年来火爆的电商新模式&#xff0c;其中的分销功能更是备受关注。本文将以分销功能为主要主题&#xf…

Rockchip RK3399 - DRM crtc基础知识

一、LCD硬件原理 1.1 CRT介绍 CRT是阴极射线管(Cathode Ray Tube)的缩写&#xff0c;它是一种使用电子束在荧光屏上创建图像的显示设备。CRT显示器在过去很长一段时间内是主流的显示技术&#xff0c;现已被液晶显示屏或其他新兴技术所替代。 在CRT显示器中&#xff0c;扫描电子…

分享一下微信小程序的文章中怎么添加营销活动

在数字化时代&#xff0c;小程序已经成为企业营销的重要工具。通过小程序&#xff0c;企业可以提供更加便捷、高效的服务&#xff0c;吸引更多的用户和客户。本文将以小程序营销活动为主题&#xff0c;介绍如何在小程序文章中加入营销活动&#xff0c;提高品牌知名度和销售额。…

C/C++数据结构——队列

个人主页&#xff1a;仍有未知等待探索_C语言疑难,数据结构,小项目-CSDN博客 专题分栏&#xff1a;数据结构_仍有未知等待探索的博客-CSDN博客 目录 一、前言 二、队列的基本操作&#xff08;循环队&#xff09; 1、循环队的数据类型 2、循环队的名词解释 3、循环队的创建及…

Windows与Linux服务器互传文件

使用winscp实现图形化拖动的方式互传文件. 1.下载winscp软件并安装&#xff0c;官方地址&#xff1a; https://winscp.net/eng/index.php 2.打开软件&#xff1a; 文件协议选择scp&#xff0c;输入linux服务器的IP和端口号&#xff0c;然后输入你的用户名和密码就可以登陆了。…

基于Java的校园餐厅订餐管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

Linux 进程切换与命令行参数

假设进程1现在要切走了&#xff0c;切入进程2.那进程1就要先保存数据&#xff0c;方便以后恢复&#xff0c; 然后进程2再切走&#xff0c;进程1再把数据还原&#xff1a; 操作系统又分为实时操作系统和分时操作系统。 实时操作系统是是给操作系统一个进程&#xff0c;操作系统…

【漏洞复现】panalog日志审计系统任意用户创建漏洞和后台命令执行

漏洞描述 panalog为北京派网软件有限公司,一款流量分析,日志分析管理的一款软件。存在任意用户创建漏洞和后台命令执行漏洞,可先通过任意用户创建,然后进行后台命令执行,获取服务器权限。 免责声明 技术文章仅供参考,任何个人和组织使用网络应当遵守宪法法律,遵守公…

Vue单文件组件

一、.vue文件 我们使用Vue的单文件组件的时候&#xff0c;一个.vue文件就是一个组件。 例如我们创建一个School组件&#xff1a; 二、组件的结构 我们编写网页代码的时候有HTML结构、CSS样式、JS交互。 组件里也是同样存在这三种结构的&#xff1a; <template><d…

甘特图组件DHTMLX Gantt示例 - 如何有效管理团队工作时间?(二)

如果没有有效的时间管理工具&#xff0c;如工作时间日历&#xff0c;很难想象一个项目如何成功运转。这就是为什么我们的开发团队非常重视项目管理&#xff0c;并提供了多种选择来安排DHTMLX Gantt的工作时间。使用DHTMLX Gantt这个JavaScript库&#xff0c;您可以创建一个强大…

2024SCI经验心得分享---如何在零基础、导师基本放养的情况下---发表自己的第一篇SCI(三区)经验分享篇

本期的经验分享&#xff0c;采访到了我的一位非常非常非常优秀的师妹&#xff0c;师妹于今年6月份投稿&#xff0c;10月份录用&#xff0c;历时四个月录用了自己的第一篇SCI&#xff08;三区&#xff09;的文章图像处理类的&#xff0c;同时师妹也取得了很多其他优秀的荣誉。 众…

软考系列(系统架构师)- 2020年系统架构师软考案例分析考点

试题一 软件架构&#xff08;架构风格、质量属性&#xff09; 【问题1】&#xff08;13分&#xff09; 针对该系统的功能&#xff0c;李工建议采用管道-过滤器&#xff08;pipe and filter)的架构风格&#xff0c;而王工则建议采用仓库&#xff08;reposilory)架构风格。请指出…