MySQL MVCC机制详解

MySQL MVCC机制详解

MVCC, 是Multi Version Concurrency Control的缩写,其含义是多版本并发控制。这一概念的提出是为了使得MySQL可以实现RC隔离级别RR隔离级别

这里回顾一下MySQL的事务, MySQL的隔离级别和各种隔离级别所存在的问题。

事务是由 MySQL 的引擎来实现的,我们常见的 InnoDB 引擎它是支持事务的。

不过并不是所有的引擎都能支持事务,比如 MySQL 原生的 MyISAM 引擎就不支持事务,也正是这样,所以大多数 MySQL 的引擎都是用 InnoDB。

事务看起来感觉简单,但是要实现事务必须要遵守 4 个特性,分别如下:

  • 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样;
  • 一致性(Consistency):数据库的完整性不会因为事务的执行而受到破坏,比如表中有一个字段为姓名,它有唯一约束,也就是表中姓名不能重复,如果一个事务对姓名字段进行了修改,但是在事务提交后,表中的姓名变得非唯一性了,这就破坏了事务的一致性要求,这时数据库就要撤销该事务,返回初始化的状态。
  • 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  • 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

MySQL的四种隔离级别如下:

  • 读未提交(read uncommitted):指一个事务还没有提交时,它做的变更才能被其他事务看到;
  • 读提交(read committed),指一个事务提交之后,它所做的变更才能被其他事务看到
  • 可重复度(repeated read),指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据时一致的,这是MySQL InnoDB引擎的默认隔离级别。
  • 串行化(serializable):会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行

读未提交级别下会遇到脏读的问题,所谓脏读是指在一个事务中会读取到另一个事务没有提交的改动,例如下图中所示:

mysql

A用户在第一次查询ID=1的用户时,其年龄是10。 在这之后,B用户对ID=1的用户的age进行了修改,随后就将事务进行了回滚。但是结果A用户第二次查询ID=1的用户的年龄时发现年龄修改为了20, 即读取到了脏数据。

读提交级别下会遇到不可重复读的问题,所谓不可重复读是指在同一个事务中多次select出的数据的值发生了变化。例如下图中所示:

mysql

A用户在第一次查询ID=1的用户时,其年龄是10。 在这之后,B用户对ID=1的用户的age进行了修改,并且提交了事务,结果A用户第二次查询ID=1的用户的年龄时发现年龄修改为了20。这样的变化就是不可重复读

MySQL使用了MVCC实现了RC和RR隔离级别,这便是MVCC机制的作用。

为了更好的去理解MVCC的原理,我们需要对MySQL的undo log有一些理解。

undo log

undo log是MySQL的三大日志之一,另外两个是bin logredo log

undo log译名为回滚日志,也就是用于事务回滚的日志。在事务没有提交之前, MySQL会将用户的操作记录到回滚日志中,如果用户执行了回滚操作,则根据回滚日志执行反向操作,例如:

  • 如果用户向数据库插入了一条数据,回滚时执行反向操作,即删除该条数据。
  • 如果用户删除了数据库的一条数据,回滚时执行反向操作,则向数据库插入该条数据。
  • 如果用户更新的一条记录,则需要把原值记录下来,回滚时则执行反向操作,将该数据的值恢复为原值。

不知道看到上面的操作有没有让你联想到git revert。git是一个版本管理工具, git log便是记录了仓库的所有commit的记录。根据git的某一个commit,git revert便会生成其反向的操作。

其实undo log的思想和git是类似的。其通过隐藏列trx_id、roll_pointer将不同事务的commit按照时间线组织了起来。

隐藏列trx_id、roll_pointer的含义如下表所示:

|列名|是否必须|描述|
|trx_id|是|记录操作该行数据事务的事务ID|
|roll_pointer|是|回滚指针,指向当前记录行的undo log信息|

如下图所示,通过roll_pointer就将每个commit串成了一个版本链。

undo_log

这样的版本链便给后续的ReadView的生成提供了条件。

ReadView

ReadView类似于一个snapshot(快照),ReadView是基于undo log实现的。

下面就来看看ReadView具体是如何实现的。

ReadView记录了下面一些字段:

  • creator_trx_id: 创建该ReadView的事务的id
  • m_ids: 创建ReadView时,当前数据库活跃且未提交的事务id列表
  • up_limit_id: 创建ReadView时,当前数据库中活跃且未提交的最小事务id
  • low_limit_id: 创建ReadView时,当前数据库中分配的下一个事务的id值

利用ReadView中的这些字段就可以判断undo log版本链上的每个commit对于当前的事务而言是否是可见的。

对于undo log中的某一条记录,判断其是否可见的规则如下:

  • 如果被访问版本的 事务ID = creator_trx_id,那么表示当前事务访问的是自己修改过的记录,那么该版本对当前事务可见;
  • 如果被访问版本的 事务ID < up_limit_id,那么表示生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。
  • 如果被访问版本的 事务ID > low_limit_id 值,那么表示生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。
  • 如果被访问版本的 事务ID在 up_limit_id和m_low_limit_id 之间,那就需要判断一下版本的事务ID是不是在 trx_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。

这段逻辑写在MySQL仓库的storage/innobase/include/read0types.h文件中。

  /** Check whether the changes by id are visible.@param[in]    id      transaction id to check against the view@param[in]    name    table name@return whether the view sees the modifications of id. */[[nodiscard]] bool changes_visible(trx_id_t id,const table_name_t &name) const {//ut 忽略ut_ad(id > 0);//如果被访问版本的 事务ID = creator_trx_id,那么表示当前事务访问的是自己修改过的记录,那么该版本对当前事务可见;//如果被访问版本的 事务ID < up_limit_id,那么表示生成该版本的事务在当前事务生成 ReadView 前已经提交,所以该版本可以被当前事务访问。if (id < m_up_limit_id || id == m_creator_trx_id) {return (true);}check_trx_id_sanity(id, name);//如果被访问版本的 事务ID > low_limit_id 值,那么表示生成该版本的事务在当前事务生成 ReadView 后才开启,所以该版本不可以被当前事务访问。if (id >= m_low_limit_id) {return (false);//如果m_ids为空,则生成readview时所有的commit对于当前事务都可见} else if (m_ids.empty()) {return (true);}const ids_t::value_type *p = m_ids.data();//如果被访问版本的 事务ID在 up_limit_id和m_low_limit_id 之间,那就需要判断一下版本的事务ID是不是在 trx_ids 列表中,如果在,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被访问。return (!std::binary_search(p, p + m_ids.size(), id));}

通过源码的阅读, 也印证了上述匹配逻辑。

上面的匹配的逻辑是针对单条commit记录的。整个过程将从undo log的最新记录开始,逐条判断,如果判断结果是可见的,那么则返回该记录。如果判断结果是不可见的,则沿着undo log往下继续寻找。

整个寻找的过程可以参照下面的流程图:

readview

下面通过一些案列来加深ReadView的理解。

在下面的案例中,事务8是当前的事务,其使用了select语句查询了表中的数据,触发了readview的生成,因此creator_trx_id=8。在readview生成的时刻,当前活跃的且未提交的事务为[4,6,7,9], 因此up_limit_id=4, low_limit_id=11。

mvcc1

下面查看user表的id=1的undo log,其最新的改动是事务9提交的。 事务9满足下面的不等式,事务4 < 事务9 < 事务11, 因此需要查看事务9是否在trx_ids列表中。经过查看发现事务9在m_ids中,因此在生成readview的时刻,事务9的提交对于事务8并不可见。 因此需要往下滑动,检查undo log中次新的数据。

在undo log的次新的数据中。trx_id=8, 与creator_trx_id相等,因此对于当前事务可见。因此readview中可见的最新数据已经找到。

mvcc2

下面查看user表的id=1的undo log,其最新的改动是事务12提交的。 事务12 > low_limit_id, 事务12的提交对于事务8并不可见。 因此需要往下滑动,检查undo log中次新的数据。

在undo log的次新的数据中。trx_id=10,在 up_limit_id和m_low_limit_id 之间,且事务10不在m_ids,说明创建 ReadView 时生成事务10已经被提交,该版本可以被访问。因此因此readview中可见的最新数据已经找到。

mvcc3

下面再看一个例子,在该例子中,undo log中最新的记录的事务id是6, 事务6满足下面的不等式, 事务4 < 事务6 < 事务11, 因此下面就需要检查事务6是否在m_ids中, 因为m_ids = [4, 6, 7, 9],因此事务6在创建readview时还没有提交,因此对于当前事务而言,该条记录并不可见。 因此沿着undo log往下找。

undo log中第二新的记录的事务id是14,事务14 > low_limit_id, 显而易见, 事务14的改动对于当前事务是不可见的。因此继续undo log往下找。

undo log中第三新的记录的事务id是5,事务4 < 事务5 < 事务11, 显而易见, 因此下面就需要检查事务5是否在m_ids中, 因为m_ids = [4, 6, 7, 9],因此事务5在创建readview时已经提交了,于是事务5对于当前事务而言是可见的, 于是找到了所需的值。

mysql

通过这三个案列对MVCC的工作机制会有非常深刻的理解了。

MVCC如何实现读提交和可重复读

读提交和可重复读的MVCC机制是相同的。区别在于ReadView的生成时机不同。

对于读提交级别而言,其会在每一次查询操作时生成一次ReadView。因此后续再次select时,就可以读取到这期间的提交。

对于可重复读级别而言,其只会在事务的第一次查询操作时生成ReadView, 于是在ReadView生成后提交的commit就不再会看到,就好像是在对一个snapshot操作一样。

mysql

参考文章

https://www.cnblogs.com/qdhxhz/p/15750866.html

https://www.cnblogs.com/cswiki/p/15338928.html

https://www.6hu.cc/archives/86666.html

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

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

相关文章

redis基线检查

1、禁止使用 root 用户启动 | 访问控制 描述: 使用root权限来运行网络服务存在较大的风险。Nginx和Apache都有独立的work用户,而Redis没有。例如,Redis的Crackit漏洞就是利用root用户权限替换或增加authorize_keys,从而获取root登录权限。 加固建议: 使用root切换到re…

新版软考高项试题分析精选(二)

请点击↑关注、收藏&#xff0c;本博客免费为你获取精彩知识分享&#xff01;有惊喜哟&#xff01;&#xff01; 1、除了测试程序之外&#xff0c;黑盒测试还适用于测试&#xff08; &#xff09;阶段的软件文档。 A.编码 B.总体设计 D.数据库设计 C.软件需求分析 答案&a…

NSSCTF第12页(2)

[CSAWQual 2019]Unagi 是xxe注入&#xff0c;等找时间会专门去学一下 XML外部实体&#xff08;XXE&#xff09;注入 - 知乎 【精选】XML注入学习-CSDN博客 【精选】XML注入_xml注入例子-CSDN博客 题目描述说flag在/flag下 发现有上传点&#xff0c;上传一句话木马试试 文件…

一文搞懂CAN总线协议

1.基础概念 CAN 是 Controller Area Network 的缩写&#xff08;以下称为 CAN&#xff09;&#xff0c;是 ISO 国际标准化的串行通信协议。在北美和西欧&#xff0c;CAN 总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线&#xff0c;并且拥有以 CAN 为底层…

指针传2

几天没有写博客了&#xff0c;怎么说呢&#xff1f;这让我总感觉缺点什么&#xff0c;心里空落落的&#xff0c;你懂吧&#xff01; 好了&#xff0c;接下来开始我们今天的正题&#xff01; 1. ⼆级指针 我们先来看看代码&#xff1a; 首先创建了一个整型变量a&#xff0c;将…

在 HarmonyOS 上实现 ArkTS 与 H5 的交互

介绍 本篇 Codelab 主要介绍 H5 如何调用原生侧相关功能&#xff0c;并在回调中获取执行结果。以“获取通讯录”为示例分步讲解 JSBridge 桥接的实现。 相关概念 Web组件&#xff1a;提供具有网页显示能力的 Web 组件。 ohos.web.webview&#xff1a;提供 web 控制能力。 …

<C++> 优先级队列

目录 前言 一、priority_queue的使用 1. 成员函数 2. 例题 二、仿函数 三、模拟实现 1. 迭代器区间构造函数 && AdjustDown 2. pop 3. push && AdjustUp 4. top 5. size 6. empty 四、完整实现 总结 前言 优先级队列以及前面的双端队列基本上已经脱离了队列定…

Ansible 企业实战详解

一、ansible简介1. ansible是什么2.ansible的特点ansible的架构图 二、ansible 任务执行1、ansible 任务执行模式2、ansible 执行流程3、ansible 命令执行过程 二 .Ansible安装部署1.yum安装2.ansible 程序结构3、ansible配置文件查找顺序4、ansible配置文件5.ansible自动化配置…

electronjs入门-编辑器应用程序

我们将在Electron中创建一个新项目&#xff0c;如我们在第1章中所示&#xff0c;名为“编辑器”&#xff0c;我们将在下一章中使用它来创建编辑器&#xff1b;在index.js中&#xff0c;这是我们的主要过程&#xff1b;请记住为Electron软件包放置必要的依赖项&#xff1a; npm…

uniapp基础学习笔记01

文章目录 本博客根据黑马教程学习uniapp一、技术架构二、创建项目2.1 Hbuilder创建2.2 插件安装2.3 微信开发者工具配置与运行2.3.1 简单修改基础页面 2.4 pages.json和tabBar2.4.1 pages.json与tabBar配置2.4.2 案例 三、uniapp与原生开发的区别 本博客根据黑马教程学习uniapp…

安装node.js指定任意版本详解

Node.js是一种基于Chrome V8引擎的JavaScript运行时环境。它允许开发人员使用JavaScript编写服务器端和网络应用程序。与传统的JavaScript在浏览器中执行不同&#xff0c;Node.js使得JavaScript可以在服务器端运行。 Node.js具有以下特点&#xff1a; 1. 非阻塞式I/O&#xf…

Java设计模式-结构型模式-适配器模式

适配器模式 适配器模式应用场景案例类适配器模式对象适配器模式接口适配器模式适配器模式在源码中的使用 适配器模式 如图&#xff1a;国外插座标准和国内不同&#xff0c;要使用国内的充电器&#xff0c;就需要转接插头&#xff0c;转接插头就是起到适配器的作用 适配器模式&…

springboot国际化

1.环境配置 这里插入图片描述](https://img-blog.csdnimg.cn/024d6bc95623485eb6da4d998a892458.png) 2.文件配置 第一个默认环境 第二个英文环境 第三个中文环境 3.变量配置 调整语言 原理&#xff1a; 找到MessageSourceAutoConfiguration 中的 利用代碼的方式獲取国…

图论11-欧拉回路与欧拉路径+Hierholzer算法实现

文章目录 1 欧拉回路的概念2 欧拉回路的算法实现3 Hierholzer算法详解4 Hierholzer算法实现4.1 修改Graph&#xff0c;增加API4.2 Graph.java4.3 联通分量类4.4 欧拉回路类 1 欧拉回路的概念 2 欧拉回路的算法实现 private boolean hasEulerLoop(){CC cc new CC(G);if(cc.cou…

数字化转型时代,商业智能BI到底是什么?

据国际数据公司&#xff08;IDC&#xff09;预测&#xff0c;2025年时中国产生的数据量预计将达48.6ZB&#xff0c;在全球中的比例为27.8%。商业智能BI这一专为企业提供服务的数据类解决方案&#xff0c;仅2021年上半年在中国商业智能BI市场规模就达到了3.2亿美元&#xff0c;商…

java入门,从CK导一部分数据到mysql

一、需求 需要从生产环境ck数据库导数据到mysql&#xff0c;数据量大约100w条记录。 二、处理步骤 1、这里的关键词是生产库&#xff0c;第二就是100w条记录。所以处理数据的时候就要遵守一定的规范。首先将原数据库表进行备份&#xff0c;或者将需要导出的数据建一张新的表了…

Java绘图-第19章

Java绘图-第19章 1.Java绘图类 1.1Graphics类 Graphics类是用于绘制图形的抽象类&#xff0c;它是java.awt包中的一部分。Graphics类提供了各种方法&#xff0c;可以在图形上绘制各种形状、文本和图像。这些方法包括画线、画矩形、画椭圆、画弧、绘制图像等。 1.2Graphics2…

Android修行手册 - 阴影效果的几种实现以及一些特别注意点

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列点击跳转>ChatGPT和AIGC &#x1f449;关于作者 专…

3D造型渲染软件DAZ Studio mac中文版介绍

DAZ Studio mac是一款3D造型和渲染软件&#xff0c;由 Daz 3D 公司开发。它允许用户创建、编辑、动画化并渲染精美的数字图像与动画。DAZ Studio 还提供了一个虚拟的3D艺术家工作室环境&#xff0c;让用户可以轻松地设置场景、布置角色和应用材质。 用户可以通过 DAZ Studio 中…

8.查询数据

一、单表查询 MySQL从数据表中查询数据的基本语为SELECT语。SELECT语的基本格式是: SELECT {* | <字段列名>} [ FROM <表 1>, <表 2>… [WHERE <表达式> [GROUP BY <group by definition> [HAVING <expression> [{<operator>…