详解MySQL的MVCC(ReadView部分解析C++源码)

news2024/10/6 20:26:29

文章目录

    • 1. 什么是MVCC
    • 2. MVCC核心组成(三大件)
      • 2.1 MVCC为什么需要三大件
    • 3. 隐藏字段
    • 4. undo log
      • 4.1 模拟版本链数据形成过程
    • 5. Read View
      • 5.1 m_ids
      • 5.2 m_creator_trx_id
      • 5.3 m_low_limit_id
      • 5.4 m_up_limit_id
      • 5.5 可见性分析算法
    • 6. MVCC流程模拟
      • 6.1 RC隔离级别
      • 6.2 RR隔离级别

  1. 以下讨论均建立在innodb存储引擎
  2. ReadView部分官方文档基本都是一笔带过,为了保证文章的正确性,这部分涉及到大量的源码分析

1. 什么是MVCC

一种数据库中用于处理并发读写事务的技术。它通过维护数据的不同版本来实现对同一数据项的并发访问,并且在保证事务隔离性的同时,允许读操作无需加锁就能获取一致性的数据视图。

2. MVCC核心组成(三大件)

在这里插入图片描述
MVCC的实现离不开上图所示的三大组件

  • 数据行的隐藏字段
  • undo log
  • read view

2.1 MVCC为什么需要三大件

我们按照逻辑来简单梳理下,为什么MVCC需要这三个部分。

首先,在并发情况下,MVCC需要维护数据行的多个版本,那多个版本信息存储在哪呢?我们可以率先排除数据库表,数据库表是存储索引+已提交数据的,再多维护一份历史数据,.idb文件要骂人了(.idb是innodb下存储表数据的文件后缀)。为了减轻.idb文件的压力,我们不妨在多创建一个文件,用于存储数据行的多个版本,而这便是undo log

我们知道多版本数据存储在undo log中,和数据表中的数据是分开存储的,那么每行数据如何找到它的历史版本呢?MySQL将数据写入数据库文件时,为多创建几个字段列,其中一个字段,就是专门存储指针,用于指向历史版本的指针。这也就是隐藏字段存在的原因

现在,MySQL的行数据能够关联到历史数据,那每个事务在查询时,获取哪个版本的数据呢?read view就是提供了数据版本确定的依据。再查询数据时,MySQL会生成read view,read view提供了选择哪个版本数据的依据

3. 隐藏字段

在这里插入图片描述

  • DB_TRX_ID:

    • 这是一个6字节长的字段,用来记录最后一次插入或更新该行记录的事务标识符。当某一行被删除时,内部会将其视为一种特殊的更新操作,在行的一个特定位上设置标记以表示其已被删除。
  • DB_ROLL_PTR:

    • 也称为回滚指针,是一个7字节长的字段。它指向回滚段中的一个undo日志记录。如果该行被更新,则undo日志记录包含重建更新前行内容所需的信息。当需要进行事务回滚或者为了实现一致性读时,可以通过回滚指针找到并应用相应的undo日志。
  • DB_ROW_ID:

    • 这是一个6字节长的行ID字段,每当新行插入时,它的值就会单调递增。如果InnoDB自动生成聚簇索引,那么这个聚集索引中就会包含行ID值。否则,DB_ROW_ID列不会出现在任何索引中。行ID可以帮助系统在没有用户定义唯一键的情况下为行提供一个唯一的标识,并且在某些查询和排序场景下也能发挥作用。

4. undo log

undo log是回滚日志,其中记录包含了关于如何撤销事务对聚集索引记录所做的最新更改的信息。事务在对数据操作前,undolog会记录数据原本的数值,方便事务回滚,回复到上一个版本的数据

undo log日志会准备两份,一份用于记录insert操作;另一份记录update 或者 delete操作

在不指定的情况下,undo log默认会在data文件夹下创建,并命名为undo_001,undo_002

在这里插入图片描述

tip:

  1. 当insert时,undo log日志只会在回滚时需要,事务提交后可被立即删除
  2. 当update、delete时,undo log日志不仅在回滚时需要、快照读时也需要,不会被立刻删除

4.1 模拟版本链数据形成过程

我们配合隐藏字段,通过undo log来模拟版本链形成的过程

  • step 1

在这里插入图片描述

一开始,事务1插入一条id = 4,age = 18的数据,数据隐藏字段DB_TRX_ID = 1(当前事务id = 1),DB_ROLL_PTR = null(没有历史版本)。此时事务2、3、4、5全部开启事务(begin)

  • step 2

在这里插入图片描述

事务2执行update操作,将id = 1数据行的age字段设置为20。MySQL更新逻辑是,先更新buffer pool中缓存的数据页,形成脏页。当事务提交后,将数据刷到磁盘中。

此时,MySQL会将当前记录存储到undo log中,用于数据回滚,其地址为0x001

  • step 3

在这里插入图片描述

事务2执行commit操作,buffer pool中的脏页被持久化到磁盘中。数据被永久更改。一方面age被设置为20,另一方面DB_TRX_ID(最新修改的事务id)设置为2,DB_ROLL_PTR指向上一个数据版本,也就是0x001位置上的数据,形成版本链

  • step 4

在这里插入图片描述
事务3执行update操作,修改缓存中的数据,同时将当前记录写入到undo log中,其地址为0x002

  • step 5

在这里插入图片描述

事务3提交事务,数据被持久化到磁盘。当前记录更改,age设置为70,DB_TRX_ID设置为3,DB_ROLL_PTR指向上一个版本的数据——0x002,追加版本链信息

  • step 6

在这里插入图片描述
事务4执行update逻辑,修改内存中的数据,并将当前记录写入undo log,为其分配地址0x003

  • step 7

在这里插入图片描述
事务4提交事务,数据被持久化。age被修改为50,DB_TRX_ID设置为当前事务id,也就是4,DB_ROLL_PTR指向上个版本的数据——0x003。至此,完整的版本连形成。

ps: 终于写完这部分了,光这个图就累死我了

5. Read View

readView视图存在如下4个字段

  • m_ids:存储当前快照生成时,所有活跃事务id
  • m_low_limit_id(max_trx_id):预分配事务id,等于当前最大事务id + 1(事务是自增的)
  • m_up_limit_id(min_trx_id):最小活跃事务id
  • m_creator_trx_id:ReadView创建者的id

在这里插入图片描述
源码分析

5.1 m_ids

	/** Set of RW transactions that was active when this snapshot
	was taken */
	ids_t		m_ids;

m_ids类型是ids_tids_t是ReadView的一个内部类

在这里插入图片描述
ids_t类中维护如下数据

		/** Memory for the array */
		value_type*	m_ptr;

		/** Number of active elements in the array */
		ulint		m_size;

		/** Size of m_ptr in elements */
		ulint		m_reserved;

		friend class ReadView;
  • m_ptr指向的是一块连续的内存空间,也就是数组。数组中每个元素的类型是value_type,追溯源码后发现value_type是无符号64位整数。为了方便后文叙述,我们把m_ptr当成数组

    tip: ids_t维护m_ptr的增删改等方法,使得数组能够自动扩容

  • m_size维护数组的大小,ulint也是无符号64位整数

  • m_reserved维护m_ptr中元素个数

现在,我们可以初步总结m_ids的作用。m_ids中维护了一个数组,而该数组保存了ReadView生成时,活跃的事务id。我们可以把m_ids看作成Java里的List集合

此外,还得强调一点,m_ids中存储id是按照递增顺序

在阅读源码时,笔者注意到ids_t类中声明的insert方法

在这里插入图片描述

注释中的preserving the order表明,ids_t这个类维护的数据是有序的,但因为ids_t这个类是申明在read0types.h这个头文件中,insert方法没给出实现,因此我们需要找到insert方法实现的类,阅读insert的具体代码,这样我们才能得知ids_t维护的数据是以什么顺序存储的

定位到read0read.cc,我们找到insert的实现

在这里插入图片描述
笔者已经对部分代码做出中文注解,故额外的逻辑不再赘述,感兴趣的读者可以自行阅读。

我们主要关注定位的逻辑

	value_type*	ub = std::upper_bound(data(), end, value);
	if (ub == end) {
		push_back(value); 
	} 

源码中调用std标准库提供的upper_bound函数,该函数底层是二分查找,目的是在给定地址范围内,找到大于目标数据的地址,并返回。如果找不到,则返回末尾迭代器(指向数组的最后一个元素的下一个位置)

insert方法中,upper_bound的目的是在m_ptr指向的数组中,找到一个最小大于value的元素,并返回它的地址。在源码中,value_type* ub就是指向value应该插入的地址。

如果ub指向end,也就是数组末尾,这意味着m_ptr指向的数组中,不存在大于value的元素,需要将value插入的数组末尾。这说明m_ptr数组中,最大的元素存储在末尾,这就表明了ids_t维护的数组是升序

5.2 m_creator_trx_id

这个字段没啥好说的,谁创建ReadView,就赋值谁的id

5.3 m_low_limit_id

	/** The read should not see any transaction with trx id >= this
	value. In other words, this is the "high water mark". */
	trx_id_t	m_low_limit_id;

我们注意到,注释中并未直接解释m_low_limit_id是什么含义,我们只知道如当事务id如果 >= readView.m_low_limit_id,事务是不可见的。

m_low_limit_id就是高水位,事务id 大于这个值,那么事务就是不可见的。但它为什么是预分配事务id呢?

我们定位到m_low_limit_id赋值的地方

在这里插入图片描述
m_low_limit_id = trx_sys->max_trx_id这行代码可知,m_low_limit_id的值是事务系统中的max_trx_id

我们继续查看max_trx_id相关代码

发现如下信息

在这里插入图片描述
max_trx_id 是还未被分配的最小事务id,也就是预分配id,同样也可以被等价理解为最大事务id(因为事务id是自增的,还未被分配的自然是已有的事务id的最大值)

因此我们得出结论,m_low_limit_id基本等价于max_trx_id,是预分配的id,等于已存在最大事务id + 1

5.4 m_up_limit_id

由源码注释可知,m_up_limit_id是"低水位",事务id小于这个值的,都是可见的

在这里插入图片描述

但它为什么是当前活跃事务的最小事务id呢?

我们定位到赋值时的源码

在这里插入图片描述
由注释可知,m_up_limit_id存储的是最小活跃id。其实看到注释就已经很明白了,但笔者想要结合前文说到m_ids的一个特点来讲解我们如何得到这个结论。

观察这段代码 m_up_limit_id = !m_ids.empty() ? m_ids.front() : m_low_limit_id;,我们发现在正常情况下,m_up_limit_id取的是m_ids.front()

m_ids.front()弹出的是数组第一个元素,m_ids维护的数据是升序!!!,因此第一个元素就是当前活跃的最小id
在这里插入图片描述

tip: m_ids数组中的id是升序,这个结论在m_ids这个章节已经详细介绍,这里不在赘述

5.5 可见性分析算法

在这里插入图片描述
可见性分析算法,能够判断undo log版本链上的版本是否可见。MySQL需要将版本对应的事务id和ReadView中的数据进行判断。

判断规则总结如下

  1. id == m_creator_trx_id ? 可以访问该版本。因为数据就是当前事务更改的
  2. id < m_up_limit_id (min_trx_id)?可以访问该版本,此时数据已经commit了
  3. id > m_low_limit_id(max_trx_id)?不能访问该版本,当前事务是在ReadView创建后开启的
  4. id不在m_ids中?可以访问该版本,因为m_ids存储活跃id,不活跃说明已经commit了

6. MVCC流程模拟

至此,我们介绍完MVCC所有组件,现在我们复用"模拟版本连数据形成"章节出现的案例,结合ReadView讲解MVCC是如何工作的

tip:
当事务的隔离级别是READ UNCOMMITED,SERIALIZABLE时,MVCC基本不起作用

  • READ UNCOMMITED,读未提交。都读未提交了,并发管理个锤子。全部脏读,爱咋地咋地
  • SERIALIZABLE,串行化。直接加锁锁定数据,只允许有一个事务控制被修改的数据,其他事务都得等着。这有个锤子的并发

6.1 RC隔离级别

RC隔离级别,每次select都会创建新的ReadView

我们以事务5第一次select时的情况分析

在这里插入图片描述
此时我们将ReadView和undo log版本链上的数据进行比对,调用可见性分析算法分析当前undo log数据链上的版本,哪个是可见的

在这里插入图片描述
首先是版本链上最新的数据,也就是地址在0x002的数据。这个数据版本是被trx_id = 2的事务创建。trx_id = 2和ReadView进行分析,我们发现 id < min_trx_id,因此当前数据可以被访问。

检查原图也好理解,在事务5 select之前,数据就已经被事务2提交了。因此,本次查询到的数据是
id = 1, age = 20

在这里插入图片描述


现在,我们以事务5第二次select为例,进行分析

在这里插入图片描述
我们将undo log版本链上的数据和ReadView中的数据进行比较

在这里插入图片描述
我们发现0x003上的数据,trx_id = 3,小于ReadView的min_trx_id。这也就说明,0x003版本的数据在ReadView生成之前被创建,可以被访问。

在这里插入图片描述
检查原图,逻辑无误。因此,本次查询到的数据是
id = 1, age = 70

6.2 RR隔离级别

在RR隔离级别下,ReadView只会以第一次创建的ReadView为准。因此事务5第一次查询,ReadView视图和RC级别下的第一次查询没有区别。查询得到的数据是id = 1,age = 20

但RR级别下,第二次的ReadView依然不变。此时ReadView和undo log情况如下图所示

在这里插入图片描述
首先判断0x003的数据,trx_id = 3不满足ReadView中的任何可见性判断规则,因此判断0x002版本的数据。

0x002版本的数据trx_id = 2,小于ReadView的min_trx_id,因此可见,所以此时查询到的数据依然是id = 1, age = 20

这也就实现了可重复读隔离级别

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

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

相关文章

三、NLP中的句子关系判断

句子关系判断是指判断句子是否相似&#xff0c;是否包含&#xff0c;是否是问答关系等&#xff0c;常应用在文本去重、检索&#xff08;用户输入和文档的相关性&#xff09;、推荐&#xff08;和用户喜好文章是否相似&#xff09;等场景中。 3.0、文本相似度计算 3.0.0 传统机…

白嫖AWS云服务器,验证、注册指南

背景 不知道你想不想拥有一台属于自己的云服务器呢&#xff0c;拥有一台自己的云服务器可以建站&#xff0c;可以在上面搭建个人博客&#xff0c;今天我就来教大家如何申请亚马逊 AWS 免费云服务器&#xff0c;这个云服务器可以长达12个月的免费。而且到期后可以继续换个账号继…

Vulnhub - DC-1

希望和各位大佬一起学习&#xff0c;如果文章内容有错请多多指正&#xff0c;谢谢&#xff01; 个人博客链接&#xff1a;CH4SER的个人BLOG – Welcome To Ch4sers Blog DC-1 靶机下载地址&#xff1a;DC: 1 ~ VulnHub 0x01 信息收集 Nmap扫描目标主机&#xff0c;发现开…

OPENCV(0-1之0.2)

OPENCV-0.2 学习安排图像基础像素访问和修改像素值 色彩空间转换RGB到灰度的转换RGB到HSV的转换 图像操作裁剪缩放旋转和翻转 图像滤波平滑和模糊图像边缘检测 图像变换仿射变换透视变换 总结 官方文档 学习安排 图像基础 像素&#xff1a;了解像素的概念&#xff0c;包括像素…

Filebeat(Beats)详细介绍与使用

1. 什么是 Beats? Beats 是开源数据传送器&#xff0c;将其作为代理安装在服务器上&#xff0c;以将操作数据发送到 Elasticsearch。 Elastic提供Beats用于捕获&#xff1a; 审核数据&#xff1a;Auditbeat 日志文件和日志&#xff1a;Filebeat 云数据&#xff1a;Functio…

【物联网学习笔记】CubeMx6.10+STM32F407ZGT6+LWIP最最最基础配置

使用正点原子探索者开发板 板载芯片&#xff1a;STM32F407ZGT6 PHY芯片&#xff1a;LAN8720 LWIP版本&#xff1a;2.1.2 本次目标&#xff1a;先ping通开发板再说&#xff01; 文章目录 一、CubeMX配置1、时钟配置2、ETH配置3、LWIP配置4、添加LAN8720的复位引脚 二、Keil工…

单链表——增删查改

本节复习链表的增删查改 首先&#xff0c; 链表不是连续的&#xff0c; 而是通过指针联系起来的。 如图&#xff1a; 这四个节点不是连续的内存空间&#xff0c; 但是彼此之间使用了一个指针来连接。 这就是链表。 现在我们来实现链表的增删查改。 目录 单链表的全部接口…

JVM之调优(一)

背景&#xff1a;生产环境由于堆内存较大&#xff0c;fullgc 垃圾回收导致程序卡顿问题&#xff08;假死&#xff09; 目录 一、程序卡顿导致的影响 前端页面空白后端数据重复 二、解决方法 降低堆内存大小使用合适的垃圾回收器&#xff08;可以尝试&#xff0c;还未进行测试…

【毕设级项目】基于嵌入式的智能家居控制板(完整工程资料源码)

基于嵌入式的智能家居控制板演示效果 基于嵌入式的智能家居控制板 前言&#xff1a; 随着科技的不断进步&#xff0c;物联网技术得到了突飞猛进的发展。智能家居是物联网技术的典型应用领域之一。智能家居系统将独立家用电器、安防设备连接成一个具有思想的整体&#xff0c;实现…

Android Studio入门——页面跳转

1.工程目录 2.MainActivity package com.example.demo01;import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCo…

数据集成工具 ---- datax 3.0

1、datax: 是一个异构数据源离线同步工具&#xff0c;致力于实现关系型数据库&#xff08;mysql、oracle等&#xff09;hdfs、hive、hbase等各种异构数据源之间的数据同步 2、参考网址文献&#xff1a; https://github.com/alibaba/DataX/blob/master/introduction.mdhttps:/…

pycharm配置解释器

pycharm配置解释器 1.mac配置解释器 1.mac配置解释器

【C++庖丁解牛】STL简介 | string容器初次见面

&#x1f341;你好&#xff0c;我是 RO-BERRY &#x1f4d7; 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f384;感谢你的陪伴与支持 &#xff0c;故事既有了开头&#xff0c;就要画上一个完美的句号&#xff0c;让我们一起加油 目录 1. 什么是STL2. STL的版本…

扫描全能王发布2023“绿色数据”:减碳超12万吨,相当于多种700万棵树

近年来&#xff0c;“绿色转型”“低碳生活”成为全民热议话题。从全国覆盖率越来越高的“垃圾分类”&#xff0c;到蓬勃发展的“无纸化办公”&#xff0c;低碳生活、绿色消费的环保风尚不断兴起。植树节将至&#xff0c;合合信息旗下扫描全能王发布了年度用户文档扫描数据&…

详细分析Mysql中的LOCATE函数(附Demo)

目录 1. 基本概念2. Demo3. 实战 1. 基本概念 LOCATE()函数在SQL中用于在字符串中查找子字符串的位置 它的一般语法如下&#xff1a; LOCATE(substring, string, start)LOCATE()函数返回子字符串在主字符串中第一次出现的位置 如果未找到子字符串&#xff0c;则返回0 具体的…

如何在群晖用Docker本地搭建Vocechat聊天服务并无公网ip远程交流协作

文章目录 1. 拉取Vocechat2. 运行Vocechat3. 本地局域网访问4. 群晖安装Cpolar5. 配置公网地址6. 公网访问小结 7. 固定公网地址 如何拥有自己的一个聊天软件服务? 本例介绍一个自己本地即可搭建的聊天工具,不仅轻量,占用小,且功能也停强大,它就是Vocechat. Vocechat是一套支持…

【BFS二叉树】113路径总和II

113路径总和 II 给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 思路&#xff1a; 题目最终输出的是路径&#xff0c;因此用BFS遍历的时候&#xff0c;需要记录走到每个节点的路径&#xff1…

ISIS单区域实验简述

ISIS 中间系统到中间系统&#xff0c;也是链路状态协议&#xff0c;工作在数据链路层&#xff0c;不依赖IP地址&#xff1b;与OSPF一样采用最短路径SPF算法&#xff0c;收敛速度快。 实验基础配置&#xff1a; r1: sys sysname r1 undo info enable int g0/0/0 ip add 12.1.1.1…

基于XMind的E-R图制作【笔记】

基于XMind的E-R图制作【笔记】 前言版权基于XMind的E-R图制作1.打开XMind2.选择模板3.插入一个自由主题4.为它插入子主题5.快速插入子主题6. 统一设置子主题样式 最后 前言 2024-3-11 10:36:33 以下内容源自《【创作模板】》 仅供学习交流使用 版权 禁止其他平台发布时删除…

js【详解】ajax (含XMLHttpRequest、 同源策略、跨域)

ajax 的核心API – XMLHttpRequest get 请求 // 新建 XMLHttpRequest 对象的实例 const xhr new XMLHttpRequest(); // 发起 get 请求&#xff0c;open 的三个参数为&#xff1a;请求类型&#xff0c;请求地址&#xff0c;是否异步请求&#xff08; true 为异步&#xff0c;f…