【MySQL】MVCC原理分析 + 源码解读 -- 必须说透

news2025/1/18 10:52:32

文章目录

  • 前言
  • 一、MVCC 介绍
  • 二、MySQL MVCC 介绍
  • 三、MySQL MVCC实现原理+源码分析
    • 3.1 隐式字段
      • 源码验证
    • 3.2 undo log
      • undo log格式
      • undo log源码验证
        • 写insert undo log源码
        • 写update undo log源码
        • 写undo log源码
        • roll_ptr是如何指向insert undo log的?
        • roll_ptr是如何指向update undo log的?
      • undo日志版本链演示
      • purge线程
    • 3.3、ReadView
      • readview核心字段
      • 核心字段在prepare和complete里赋值
      • 如何判断记录的可见性?
      • 搞懂了可见性,我们再看如何通过ReadView实现快照读(consistent read)?
      • 最后我们再来看下ReadView的生命周期(何时分配,保时关闭)?
  • 总结


前言

如何控制并发是数据库领域中非常重要的问题之一,MySQL为了解决并发带来的问题,设计了事务隔离机制、锁机制、MVCC机制等等,用一整套机制来解决并发问题,本文主要介绍MySQL5.7版本的MVCC机制。


一、MVCC 介绍

MVCC, 全称 Multi-Version Concurrency Control(多版本并发控制)
利用多版本解决的是读写并发冲突, 做到读写冲突时, 避免加锁, 实现非阻塞的读操作, 也就是无锁并发控制.

很多数据库也都有各自的实现,像Oracle、PostgreSQL、SQLSerer、MySQL等,但没有统一的标准,所以内部实现也各有差异。


二、MySQL MVCC 介绍

MySQL的InnoDB引擎支持MVCC, 工作原理是使用数据在某个时间点的快照来实现。这意味着,无论事务运行多长时间,都可以看到数据的一致视图,也意味着不同的事务可以在同一时间看到同一张表中的不同数据! 上文回顾:MySQL事务隔离机制 – 必须说透

为了更好的理解, 我们先了解两个重要概念:当前读和快照读

  • 当前读:官方叫做 Locking Reads(锁定读取), 读取数据的最新版本. 常见的 update/insert/delete、还有 select … for update、select … lock in share mode 都是当前读.
    官方文档:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-reads.html
  • 快照读:官方叫做 Consistent Nonlocking Reads(一致性非锁定读取), 也就是 MVCC 生成的 ReadView, 用于普通的 select 的语句.
    官方文档:https://dev.mysql.com/doc/refman/5.7/en/innodb-consistent-read.html
    这里需要多啰嗦一下consistent read(源码里到处可见):
    在这里插入图片描述
    一种读取操作, 使用数据在某个时间点的快照显示查询结果, 而不考虑同时运行的其他事务所执行的更改. 如果查询的数据已被另一个事务更改, 则会根据undo log的内容重建原始数据. 该技术避免了一些锁定问题,这些问题可以通过强制事务等待其他事务完成来减少并发性.

对于REPEATABLE READ隔离级别, 快照基于执行第一次读取操作的时间. 使用READ COMMITTED隔离级别,快照将重置为每次一致读取操作的时间.

一致读取是InnoDB以READ COMMITTED和REPEATABLE READ隔离级别处理SELECT语句的默认模式. 由于一致读取不会对其访问的表设置任何锁,因此在对表执行一致读取时, 其他会话可以自由修改这些表.
官方文档:https://dev.mysql.com/doc/refman/5.7/en/glossary.html#glos_consistent_read


三、MySQL MVCC实现原理+源码分析

MySQL InnoDB引擎实现的MVCC, 主要依赖数据行的隐式字段与undo log生成的日志版本链, 再结合ReadView可见性判断机制实现.

3.1 隐式字段

在内部,InnoDB向数据库中存储的每一行添加三个字段:

  • DB_TRX_ID :6 byte,插入或更新行的最后一个事务ID. (解读:用于MVCC的ReadView判断事务id)
    此外, 删除在内部被视为更新,其中行中的一个特殊位被设置为将其标记为已删除.
  • DB_ROLL_PTR:7 byte,回滚指针. (解读:用于MVCC中指向undo log记录)
    指向已写入回滚段(rollback segment)的一条undo log记录, 记录着行(row)更新前的副本.
  • DB_ROW_ID:6 byte,隐藏的自增 ID. (解读:对于MVCC可忽略该字段)
    如果InnoDB自动生成聚集索引, 则索引包含这个行ID值. 否则, DB_ROW_ID列不会出现在任何索引中.

大概是这样:
在这里插入图片描述

源码验证

在这里插入图片描述
在这里插入图片描述

参考官方:
在这里插入图片描述

3.2 undo log

undo log,撤销日志。
在事务中,insert/update/delete每一个sql语句的更改都会写入undo log,当事务回滚时,可以利用 undo log 来进行回滚。
undo 日志的存储结构比较复杂, 本文不做详细解读. 最小单元的undo log都有nextstart 两个字段, 形成一个双向链表, 示意图:
在这里插入图片描述

undo log格式

  • insert undo log
    insert undo log是指在insert操作中产生的undo log, 仅用于事务回滚. 因为insert操作的记录, 只对事务本身可见, 对其它事务不可见, 所以该日志可以在事务commit后直接删除. 不需要进行purge(后台清除线程)操作. 格式如图7-14所示.
  • update undo log
    update undo log是对delete和update操作产生的的undo log. 该undo log可能需要提供MVCC机制, 因此不能在事务commit后就进行删除. 提交时放入undo log链表,等待purge线程(后台清除线程)进行最后的删除. 格式如图7-15所示.

注:图片来源于姜承尧老师的《MySQL技术内幕 InnoDB存储引擎 第2版》
*表示对存储的字段进行了压缩
在这里插入图片描述

  • insert undo log 格式解读
字段说明
next2字节, 下一个undo log的位置.
type_cmpl1字节, undo类型. insert un log值固定为11
undo_noundo编号
table_idtable.id
lenN + colN所有列的长度和数据(插入到聚集索引中的)
start2字节, 本条undo log开始的位置.
  • update undo log 格式解读
    next、start、undo_no、table_id、lenN + colN和之前的insert undo log相同, 不做重复说明.
字段说明
DATA_TRX_ID旧记录的事务id(关键:用于MVCC的ReadView判断事务id
DATA_ROLL_PTR旧记录的回滚指针(关键:用于MVCC中指向前一个undo log记录
type_cmpl12 TRX_UNDO_UPD_EXIST_REC 更新non-delete-mark的记录
13 TRX_UNDO_UPD_DEL_REC 将delete的记录标识为not delete
14 TRX_UNDO_UPD_DEL_MARK_REC 将记录标为delete
update_vector表示update操作导致发生改变的列. (不做详细解读)

undo log源码验证

写insert undo log源码

入口函数:trx_undo_page_report_insert
在这里插入图片描述

写update undo log源码

入口函数:trx_undo_page_report_modify
这里只贴最关键的写trx_id和roll_ptr:
在这里插入图片描述

写undo log源码

不管是insert undo log还是update undo log,
它们只有一处调用:trx_undo_report_row_operation
在这里插入图片描述
最后写完undo log构建当前undo log的roll_ptr
在这里插入图片描述

roll_ptr是如何指向insert undo log的?

入口函数:btr_cur_ins_lock_and_undo
在这里插入图片描述
调用row_upd_index_entry_sys_field设置聚集索引中的trx_id和roll_ptr
在这里插入图片描述

roll_ptr是如何指向update undo log的?

我们已知写undo log的统一入口是 trx_undo_report_row_operation , 我们先看调用它的函数: btr_cur_upd_lock_and_undo
从注释可以看出:对于更新,检查锁并追加undo log
另外,如果不是聚集索引就不会写undo log,看红框 We do undo logging only when we update a clustered index record(只有在更新聚集索引记录时,才写undo log)
在这里插入图片描述
咱们再找btr_cur_upd_lock_and_undo的引用,就找到了btr_cur_update_in_place
在这里插入图片描述
row_upd_rec_sys_fields
Updates the trx id and roll ptr field in a clustered index record when a row is updated or marked deleted.
当行被更新或标记为删除时,更新聚集索引记录中的trx-id和roll-ptr字段。
在这里插入图片描述

undo日志版本链演示

为了演示效果, 这里我们采用可重复读(RR)级别来演示, 演示一下undo日志版本链是如何在读写并发时读到不同版本的,

我们采用6个事务,1个事务insert, 2个事务update,另外3个事务读到3个版本的效果.

执行时间轴从上至下预览(下面有分步的undo log和回滚指针指向说明,readview看不懂先忽略,讲完readview再回头来看,
格式:readview[m_ids], m_low_limit_id 其中m_ids最小的是m_up_limit_id):

在这里插入图片描述

  • 事务100 对user表执行 insert + 提交
    说明:这步就是为了构建原始记录
begin;
insert into user(id,name) values(1,'张三');
commit;

在这里插入图片描述

  • 然后, 事务101 对user表执行 update + 提交
begin;
update user set name = '李四' where id = 1;
commit;

在这里插入图片描述

  • 同时, 事务102事务101提交前, 查询了该记录:
    说明:有事务使用的undo log, purge线程不会清除这条记录
begin;
-- 读到的name为张三
select * from user where id = 1; 
  • 同时, 事务103 对user表执行 update 未提交
begin;
update user set name = '王五' where id = 1;

在这里插入图片描述

  • 然后, 事务102 再次读取: 在RR级别, 后面所做的更改依然不可见:
begin;
-- 读到的name为张三
select * from user where id = 1;

-- 读到的name仍为张三
select * from user where id = 1;
  • 同时, 事务104 执行了查询, 读取到了事务101提交的“李四”(因为事务103尚未提交):
begin;
-- 读到的name为李四
select * from user where id = 1;
commit;
  • 然后, 事务103 提交, 事务105来了, 它读取到的却是“王五”. 3个事务看到了3个版本, 版本链就是这样的, 还可能会更多版本…

purge线程

上述提到的 「purge 线程],是一个周期运行的垃圾收集线程, 对于没有事务引用的undo log进行清除, 上面演示里由于都有事务读取, 所以purge线程不会清除. 但当purge线程发现undo log没有事务引用时将自动清除. 另外, 从上面我们得知, 清除的是update undo log, 因为insert undo log在事务完成时直接删除.

  • innodb 会将所有需要清理的任务添加到 purge 队列中,可以通过 innodb_max_purge_lag 配置项设定 purge 队列的大小
  • 通过show variables like ‘%purge%’ 查看purge参数

本文由 [天罡gg] 首发于csdn,转载请注明出处:https://blog.csdn.net/scm_2008/article/details/127985117

3.3、ReadView

一听到view,大家可能会联想到数据库视图,然后可能会误解为把数据库当前所有表都通过视图快照起来,其实不是,那样的话得多占空间,性能得多差啊。其实生成的ReadView里面主要保存的是用于比较可见性的事务id。

readview核心字段

在这里插入图片描述
先说结论,下面再来验证

字段说明可见性说明
m_low_limit_id尚未分配的最小事务id>=它的, 都不可见
m_up_limit_id最小活动未提交事务id<它的, 都可见
m_creator_trx_id创建readview的事务id=它的, 都可见
m_ids创建readview所有活动未提交的事务ids在m_ids里面不可见,否则可见

核心字段在prepare和complete里赋值

从下面的源码里,可以验证上面4个字段的说明是准确的.
trx_sys->max_trx_id 的注释说明是:The smallest number not yet assigned as a transaction id or transaction number.
在这里插入图片描述

如何判断记录的可见性?

入口函数:changes_visible
从下面的源码里,可以验证上面4个字段的可见性说明是准确的.
在这里插入图片描述
在这里插入图片描述

搞懂了可见性,我们再看如何通过ReadView实现快照读(consistent read)?

  • 先判断聚集索引中的记录是否可见
    lock_clust_rec_cons_read_sees
    检查是否在一致读取中看到记录。
    如果可以看到,返回true;如果应检索记录的早期版本,则返回false
    在这里插入图片描述
  • 不可见时,再通过回滚指针找到可见的版本记录
    在不同的调用链路上会调下面这两个函数(注释都是一样的):
    **row_sel_build_prev_vers_for_mysql() ** // 为“一致读取”生成聚集索引记录的早期版本
    row_sel_build_prev_vers() // 为“一致读取”生成聚集索引记录的早期版本
    然后这两个函数内部都会调
    row_vers_build_for_consistent_read()
    在这里插入图片描述

最后我们再来看下ReadView的生命周期(何时分配,保时关闭)?

  • 可重复读(RR)级别
    入口函数:innobase_start_trx_and_assign_read_view
    开始事务并分配一致性读的快照readview(如果还没有)
    在这里插入图片描述
    看看内部调的trx_assign_read_view,命名非常准确!
    在这里插入图片描述
    view_open了,里面调prepare和complete生成ReadView为字段赋值了
    在这里插入图片描述
  • 读已提交(RC)级别
    对于读已提交(RC)级别,每次select都会重新分配readview,因为每次SQL语句结束后,会关闭readview
    row_sel_step
    在这里插入图片描述
    关闭在external_lock
    在这里插入图片描述

总结

通过本文我们已经详细说明了:

  1. MVCC介绍
  2. MySQL MVCC 介绍
  3. MySQL MVCC实现原理+源码分析(这里面最关键的是undo日志版本链和ReadView的可见性判断)

如果感觉不错,请关注我分享更多干货:天罡gg https://blog.csdn.net/scm_2008
大家的「关注 + 点赞 + 收藏」就是我创作的最大动力!


参考:
图文带你彻底弄懂MySQL事务原子性之UndoLog
MySQL · 引擎特性 · InnoDB undo log 漫游
MySQL 8.0 MVCC 核心原理解析(核心源码)
InnoDB MVCC实现原理及源码解析
【MySQL笔记】正确的理解MySQL的MVCC及实现原理
《MySQL技术内幕 InnoDB存储引擎 第2版》
官网14.3 InnoDB Multi-Versioning
官网14.6.3.4 Undo Tablespaces
官网14.6.7 Undo Logs
Mysql 核心日志(redolog、undolog、binlog)
图文结合带你搞定MySQL日志之Undo log(回滚日志)
MVCC详解,深入浅出简单易懂
详解 MySQL 的 undo log
庖丁解InnoDB之UNDO LOG

大家的「关注 + 点赞 + 收藏」就是我创作的最大动力!


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

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

相关文章

Thymeleaf模板

Thymeleaf可用于前后端分离&#xff0c; 下图&#xff0c;value"aa"&#xff0c; 在本地静态资源可以改变值&#xff0c;但是在web端不可以 前端可以在本地测试&#xff0c;有数据了显示数据 所以前后端分离 th属性 常用th属性解读html有的属性&#xff0c;Thymel…

集合框架----源码解读LinkedList篇

1.LinkedList官方介绍 双链表实现的list和Deque接口。实现所有可选的列表操作&#xff0c;并允许所有元素(包括null)。 所有的操作都按照双链表的预期执行。索引到列表中的操作将从列表的开始或结束遍历列表&#xff0c;以更接近指定索引的为准。 注意&#xff0c;这个实现不是…

全球价值链GVC总出口分解(2011-2014年)

1、数据来源&#xff1a;&#xff29;&#xff23;&#xff29;&#xff2f;数据库 2、时间跨度&#xff1a;2011-2014年 3、区域范围&#xff1a;世界 4、指标说明&#xff1a; 全球价值链分析(Global Value Chain analysis&#xff0c;简称GVC分析)为解决传统贸易统计中…

数据库 1.关系

从关系开始&#xff1a; Table的严格定义&#xff1a; 域就是&#xff1a;学生表{名字&#xff08;char(20),学号(int20)&#xff09;}里面的char20,int20,是用来标记列的数据类型&#xff0c;或者说取值范围的。这个取值范围有一个大小&#xff0c;这个大小就是基数。 就是每种…

世界各国自然资源租金面板数据

1、数据来源&#xff1a;世界银行《世界发展指标数据库》 2、时间跨度&#xff1a;1970-2018年 3、区域范围&#xff1a;全球 4、指标说明&#xff1a; 自然资源租金总额是石油租金、天然气租金、煤炭&#xff08;硬煤和软煤&#xff09;租金、矿产租金和森林租金之和。 …

11.20 至 11.27 五道典型题记录: 贪心 | 应用题 | 脑筋急转弯 | 区间问题 | 双指针

11.20 至 11.27 五道典型题记录&#xff1a; 贪心 | 应用题 | 脑筋急转弯 | 区间问题 | 双指针 松懈了最近&#xff0c;要时刻保持警醒啊&#xff01;学习不能停&#xff0c;说那么多的借口不如花一些心思去学一些知识&#xff0c;之所以学到的内容不成体系&#xff0c;一方面就…

【Java集合】集合是什么?为什么要用集合?

> 集合是什么&#xff1f;为什么要用集合&#xff1f; 保存数据会经常使用到数组&#xff0c;但数组存在以下几个缺陷: 长度开始时必须指定&#xff0c;且一旦指定&#xff0c;不能更改&#xff1b;保存的必须为同一类型的元素&#xff1b;使用数组进行增加元素的步骤比较麻…

MySQL 8.0 Data Dictionary显示

数据字典 对于MySQL的系统库都不会陌生&#xff0c;因为是基本框架&#xff0c;支撑着MySQL有效运行。这些系统库提供诸多功能&#xff0c;如&#xff1a;账号&#xff0c;表&#xff0c;存储过程&#xff0c;表空间&#xff0c;性能监控&#xff0c;配置 等基础信息。系统库目…

【前沿技术RPA】 一文了解UiPath 使用Git管理项目

&#x1f40b;作者简介&#xff1a;博主是一位.Net开发者&#xff0c;同时也是RPA和低代码平台的践行者。 &#x1f42c;个人主页&#xff1a;会敲键盘的肘子 &#x1f430;系列专栏&#xff1a;UiPath &#x1f980;专栏简介&#xff1a;UiPath在传统的RPA&#xff08;Robotic…

【soc】— spluboot校验方法

【soc】— spl/uboot校验方法 一.常规校验/外部有存储介质 针对外部有存储介质的如nandFlash&#xff0c;norFlash&#xff0c;emmc&#xff0c;Sd等&#xff0c;常用的校验方法为&#xff1a;headerspl/uboot header&#xff1a;可定义为结构体&#xff0c;内容包括&#x…

全国366个市县日度空气质量数据(2016-2020年)(AQI,SO2,NO2,PM2.5,PM10)

数据集名称&#xff1a;全国366个市县日度空气质量数据 时间范围&#xff1a;2016-2020年 相关说明&#xff1a;共收录366个市县全年全日数据&#xff0c;其中浓度为日均值&#xff0c;IAQI由浓度推算而来。IAQI为各空气质量指标对应的空气质量指数&#xff0c;用于对应AQI与…

多线程的初识

目录多线程线程的引入进程和线程的关系多线程可能存在的问题多线程程序的创建Thread创建第一个多线程程序线程的抢占式执行查看java进程中的所有线程用Thread的其他方法创建多线程实现Runnable接口使用匿名内部类&#xff0c;继承Thread使用匿名内部类实现Runnable使用Lambda表…

嵌入式Linux驱动开发笔记(未完待续。。。)

一、Git仓库用法 1、linu终端输入下面命令安装 git clone https://e.coding.net/weidongshan/linux_course/linux_basic_develop.git2、 进入到GIT仓库目录 cd /D/abc/doc_and_source_for_mcu_mpu在doc_and_source_for_mcu_mpu目录下&#xff0c;执行以下命令获得资料的最新…

【1752. 检查数组是否经排序和轮转得到】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个数组 nums 。nums 的源数组中&#xff0c;所有元素与 nums 相同&#xff0c;但按非递减顺序排列。 如果 nums 能够由源数组轮转若干位置&#xff08;包括 0 个位置&#xff09;得到&#xf…

appnium环境搭建

一、安装JDK 官网下载对应版本直接安装 二、安装Android SDK 官网下载对应版本直接安装 https://www.androiddevtools.cn/ 三、安装安卓模拟器 我使用的是夜神模拟器&#xff0c;官网下载直接安装 夜神安卓模拟器-安卓模拟器电脑版下载_安卓手游模拟器_手机模拟器_官网 …

springboot整合SpringSecurity并实现简单权限控制

目录 一、SpringSecurity介绍 案例效果&#xff1a; 二、环境准备 2.1 数据库 2.2 项目准备 三、确保项目没问题后开始使用 3.1、Security的过滤链&#xff1a; 3.2、自定义用户名密码登录&#xff1a; 方式1&#xff1a;将用户名密码写在配置文件里 方式2&#xff1a;使…

刷题之莲子的软件工程学和机械动力学以及物理热力学

目录 1、莲子的软件工程学 1&#xff09;题目 2&#xff09;题目解析 3&#xff09;代码 2、莲子的机械动力学 2&#xff09;题目解析 3&#xff09;代码 3、莲子的物理热力学 1&#xff09;、题目 2&#xff09;题目解析 1、莲子的软件工程学 1&#xff09;题目 题目背景…

Linux下的进程控制-进程程序替换

这篇主要说一下Linux下的进程控制中最后一部分内容&#xff1a;进程程序替换。 文章目录1. 进程程序替换1.1 为什么要进程程序替换1.2 替换原理1.3 如何进行程序替换1.3.1 execl函数1.3.2 引入子进程的程序替换1.3.3 execv函数1.3.4 execlp函数和execvp函数1.3.5 如何执行其它…

Flutter自定义对话框返回相关问题汇总

Flutter自定义对话框&#xff0c;禁用系统返回按钮 - WillPopScope 使用WillPopScope即可&#xff0c;重点onWillPop方法: Future<bool> _onWillPop()>new Future.value(false); 由于要弹出dialog&#xff0c;我这里是禁掉返回按钮&#xff0c;当然也可以在这里做一下…

基于SpringBoot的二手商品交易平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SpringBoot 前端&#xff1a;Vue、HTML 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#…