MySQL进阶突击系列(05)突击MVCC核心原理 | 左右护法ReadView视图和undoLog版本链强强联合

server/2025/1/13 10:25:43/

2024小结:在写作分享上,这里特别感谢CSDN社区提供平台,支持大家持续学习分享交流,共同进步。社区诚意满满的干货,让大家收获满满。

对我而言,珍惜每一篇投稿分享,每一篇内容字数大概6000字左右,加上画图,以及案例demo代码编写、实战,撰稿时长平均3小时左右。由于年底工作特别忙,晚上下班回家,有时候娃已经睡着了,如果娃没睡还得陪娃玩直到她睡着才有空继续写作。每天空闲时间非常少,经常一篇文章从周一写到周末才能完成。

近5个月以来投稿并不多,仅29篇。新的一年,争取有更多时间,和大家交流学习分享,包括家庭、日常、职场其他非技术性内容。

一、前言背景

二、通俗演义-MVCC多版本并发控制核心原理

2.1 解密-基于undoLog实现的数据版本链

2.2 弯弯绕绕看不懂的readView视图-一句话总结看懂

三、MVCC解决脏读、不可重复读、幻读问题demo详解

3.1 验证MVCC解决脏读、不可重复读问题【并发事务一个重复查+另一个改】

3.2 验证MVCC解决幻读、不可重复读问题【并发事务一个重复查,一个新增】

四、脏写是什么?如何解决


【公众号搜索:拉丁解牛说技术】欢迎同学们关注交流讨论。

一、前言背景

之前系列4文章说过MySQL InnoDB存储引擎,默认事务隔离级别是可重复读repeatable-read。我们可通过命令查看:SELECT @@SESSION.tx_isolation;

而且也说到,MySQL的可重复读事务隔离级别,可以解决脏读、幻读、不可重复读三大事务并发问题。当时也留了一个思考题:MySQL是如何做到的?答案是MVCC+锁。核心在于MVCC。

那MySQL如何让实现一个事务多次读,不受另一个事务的增、改、删的影响。带着这个问题,我们一步步解密MySQL的MVCC多版本并发控制核心机制。

二、通俗演义-MVCC多版本并发控制核心原理

MVCC,全称是Multi Version Concurrency Control多版本并发控制。MySQL innoDB存储引擎,在新增修改删除数据的时候,并没有真正用新数据直接更新覆盖,而是采用版本链方式去保存数据修改记录。每个读事务,对应一个版本的数据快照。每个写事务,在事务提交之前,该事务内做的任何更新操作未提交之前,其他任何事务不可见该更新。

举一个通俗的案例,也是我们日常实践的数据版本管理。比如用户信息user (id,name,age,city,cs_level)修改,我们不会简单的直接进行update,通常会进行数据历史记录。比如最简单的user表增加一个is_valid字段,利用主键id自增的特性,把它当做用户信息更新版本号。每次更新用户信息,将原信息is_valid置为false。然后用新信息去构建一条is_valid=true数据。这样就可以完成版本记录追溯。

MVCC的数据快照、数据版本链就是是类似效果。但是在并发事务里读写,具体原理会复杂很多。

2.1 解密-基于undoLog实现的数据版本链

在MySQL表里,有2个隐藏的字段,一个是事务的ID:trx_id,这个事务id就是最近一次更新该数据的事务id;另一个是回滚指针:roll_pointer ,该指针指向的就是更新该数据之前的undoLog,可以通俗理解为:修改前数据。据此,隐藏的事务id,和回滚指针的意义,一目了然,一个表示哪个事务更新了该数据,一个表示该数据更新前的样子。

比如下图:事务trx_id=98的操作,新增了name=【拉丁解牛说技术】的这行数据。新增数据的时候,roll_pointer是空。此后,事务99对该数据进行修改,把name改为=【老牛】。此时如下图,回滚指针指向老版本事务98的数据。这样数据版本链清晰可见。

另外说一下,在MVCC里只有更新、删除、新增操作有让事务ID新增,查询是不会让数据事务发生变化。

2.2 弯弯绕绕看不懂的readView视图-一句话总结看懂

readview顾名思义是读视图,当开启一个事务,MySQL会根据当前事务隔离级别,给你这个事务开启独立的review视图空间。这里很多博文在讲解readview机制时候,会对当前最大max_trx_id最小,min_trx_id,当前事务this_trx_id,视图开启时候活跃的事务id组等多个事务id进行比较说明,讲的非常细。不过这里的判断规则说这么细,如果读者些微没跟上,或者失去耐心,可能就错失理解掌握readview的核心机制。

我们坚持大道至简的方法,总结readview视图核心机制,最直接一句话:每个事务只能读到对应事务隔离级别的数据。

我们简单举例说一下:

如下图,当前事务隔离级别是可重复读RP,并发事务100要重复多次查询,事务101 要更新name为【zhangsan】。事务并发开始前如下:

接下来具体操作:

1、事务id=100进行查询,首先查到了事务99的数据,发现自己的事务ID100比99大,直接返回读到该数据【老牛】。

2、接下来事务id=101,把数据更新为【zhangsan】并提交更新trx_id=101,回滚指针指向了之前trx_id=99的老数据。

3、事务100,继续重复读,这时候读到了trx_id=101的最新数据,发现比自己的trx_id=100还大。在当前每个事务只能读到对应事务隔离级别的数据原则下,而且当前事务隔离级别是可重复读RP。按规则,不好意思,这个101事务修改的数据我不能读,得继续遍历undoLog版本链,找到了下一个事务id=99的数据【老牛】,发现99< 100,非常好,符合事务隔离级别要求。那这次重复读,读的还是之前的数据【老牛】。

同样道理,如果是事务隔离级别在读已提交、读未提交,判断规则也只是对应判断当前自己的事务ID与读到的数据事务id大小关系是否满足事务隔离级别即可。

当然,这里核心再详细展开确实有很多细节,比如读已提交隔离级别下,每次查询,就是开启一个新的readview。这个和可重复读隔离级别不一样。

理解了MVCC核心原理,我们设计场景,在InnoDB默认的事务隔离级别「可重复读RP」下,一步步实践验证解决脏读、幻读、不可重复读问题。

三、MVCC解决脏读、不可重复读、幻读问题demo详解

新建一个user_mvcc_demo表,多个事务并发读、修改name值、以及新增写入,来具体验证mvcc核心机制。

CREATE TABLE user_mvcc_demo (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(16) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 新增一条数据,id=1
insert into user_mvcc_demo(name)values ('拉丁解牛说技术001');

3.1 验证MVCC解决脏读、不可重复读问题【并发事务一个重复查+另一个改】

之前说过:脏读,特指的就是一个事务里select查询到另一个事务update语句未提交的脏数据场景。

本demo模拟场景:事务1里面多次重复查询id=1的name值,而事务2并发修改了name值并提交。

预期结果:在事务1里每次查询都是数据快照‘拉丁解牛说技术001’;事务2提交事务前、后,事务1均读不到name的新值zhangsan。

具体如下:

事务1 SQL:

begin;

select * from user_mvcc_demo where id=1;

select now();-- 时间 2025-01-03 16:04:46

select * from user_mvcc_demo where id=1;

多次查询

.....

事务2 SQL:

begin;

select * from user_mvcc_demo where id=1;

select now();-- 时间 2025-01-03 16:04:46

update user_mvcc_demo set='zhangsan' where id=1;-- 更新name 为zhangsan

select * from user_mvcc_demo where id=1;

select * from user_mvcc_demo where id=1;--- 此时未提交,但事务1读不到脏数据zhangsan

commit;--提交后,事务1仍然读不到新快照数据zhangsan

.....

实践结果:

在事务1内,多次查结果都是:拉丁解牛说技术001。实际上,在另一个事务2, 时间 2025-01-03 16:04:26 已经修改name为zhangsan。

且事务2提交事务后,在事务1里,继续多次查询,查到的也是事务1开启事务后,对应的快照数据:拉丁解牛说技术001,验证MVCC解决脏读、不看重复读问题完成。

如下图两个会话:

3.2 验证MVCC解决幻读、不可重复读问题【并发事务一个重复查,一个新增】

系列3具体说过,不可重复读问题:一个事务读到了另一个事务已提交的数据。主要针对的是一个事务里select到另一个事务update或者delete语句的更新结果。

而幻读:一个事务读到另一个事务新增的数据,特指一个事务里select查询查到了另一个事务insert数据。

本demo模拟场景:事务1里面多次重复查询全表,而事务2新增了一条数据并提交。

预期结果:在事务1里每次查询都只有一条数据;事务2提交事务前、后,事务1均读不到新增那条数据。

仍然是user_mvcc_demo表,里面只有一条id=1,name=‘zhangsan’;的数据。

事务1:重复读select * from user_mvcc_demo 。

事务2:新增1条数据。insert into user_mvcc_demo(name)values('拉丁解牛说技术');

具体如下:

事务1 SQL:

begin;

select * from user_mvcc_demo;

select now();-- 时间 2025-01-03 16:40:45

select * from user_mvcc_demo;

多次查询

.....

事务2 SQL:

begin;

select * from user_mvcc_demo;

select now();-- 时间 2025-01-03 16:40:22

insert into user_mvcc_demo(name)values('拉丁解牛说技术');;-- 新增了一条'拉丁解牛说技术'的数据

select * from user_mvcc_demo;

commit;--提交后,事务1仍然读不到新快照数据'拉丁解牛说技术'

.....

实践结果,与预期一致:在事务1里每次查询都只有一条数据zhangsan;而事务2提交事务前、后,事务1均读不到新增那条数据'拉丁解牛说技术'。

四、脏写是什么?如何解决

脏读,说过很多次,脏写大家听的很少。所谓脏写:就是并发事务更新同一个数据,比如2个并发事务修改id=1,的name值,之前name值是【拉丁解牛说技术】。事务1此时想改成zhansan并提交,而事务2改成lisi,但是中途回滚了,回滚为老数据【拉丁解牛说技术】。对于事务1来说,这就是脏写问题。

MySQL是通过锁机制来保障并发事务串行化执行,避免事务并发脏写问题。简单的说,更新事务读到数据后,需要先加锁,加锁成功才能开始执行更新事务。未拿到锁的事务,需要等待锁。这个和Java并发编程的锁机制类似。

篇幅有限,具体锁相关类型、以及具体场景锁分析,我们下一篇继续分享。


http://www.ppmy.cn/server/157990.html

相关文章

pytorch小记(二):pytorch中的连接操作:torch.cat(tensors, dim=0)

pytorch小记&#xff08;二&#xff09;&#xff1a;pytorch矩阵乘法&#xff1a;torch.cat&#xff08;tensors, dim0&#xff09; 语法使用规则示例 1&#xff1a;在第 0 维&#xff08;行&#xff09;拼接示例 2&#xff1a;在第 1 维&#xff08;列&#xff09;拼接示例 3&…

【Ubuntu与Linux操作系统:十、C/C++编程】

第10章 C/C编程 10.1 Linux编程基础 Linux编程基础涵盖了C/C语言在Linux环境中的特点和使用方法。Linux以其高性能和开源特性成为系统编程的重要平台。 1. C语言与Linux的关系 Linux内核主要是用C语言编写的&#xff0c;因此学习C语言是理解Linux底层机制的必要前提。C语言的…

G1原理—5.G1垃圾回收过程之Mixed GC

大纲 1.Mixed GC混合回收是什么 2.YGC可作为Mixed GC的初始标记阶段 3.Mixed GC并发标记算法详解(一) 4.Mixed GC并发标记算法详解(二) 5.Mixed GC并发标记算法详解(三) 6.并发标记的三色标记法 7.三色标记法如何解决错标漏标问题 8.SATB如何解决错标漏标问题 9.重新梳…

ElasticsearchJavaClient工具类分享

最近升级了Elasticsearch版本&#xff0c;从7.X升级到8.X的变化还是比较大的&#xff0c;原来7版本用的是RestHighLevelClient&#xff0c;8.X弃用RestHighLevelClient转而支持ElasticsearchClient&#xff0c;并且api调用方式经过建造者模式的改造&#xff0c;变成了链式调用。…

基于springboot的医药管理系统源码+论文+开题报告

系统介绍 系统中包含论文和开题报告 今的年代,已经是步入信息社会了,不仅信息更新速度频繁,信息量也大,在信息时代必须有相应的处理信息的方法,如果还采用以前的结绳记事或者笔写纸记,不仅是信息录入效率上赶不上节奏,在信息检索的速度上更是让人无法承受。幸而当今社会…

RabbitMQ消费者重试的两种方案

目录 直接重试 优点&#xff1a; 缺点&#xff1a; 保存数据库的重试方案 优点&#xff1a; 缺点&#xff1a; 选择建议 适合直接重试的场景 适合数据库记录的场景 这篇文章总结一下消费者消费重试的方案 直接重试 一种是消息消费失败然后消费者直接重试&#xff…

数据结构之双向链表

目录 双向链表的基本概念和结构 初始化 尾插 头插 尾删 头删 查找 在指定位置之后插入 删除指定位置节点 判空 销毁 完整代码 测试代码 双向链表的基本概念和结构 双向链表&#xff08;Doubly Linked List&#xff09;‌是一种链式存储结构&#xff0c;每个节点除…

spark functions函数合集(无示例)

ctrlF进行页面查找 没有示例&#xff0c;仅用于查询&#xff0c;具体用法自行搜索 函数名称作用avg计算指定列的平均值count计算指定列或所有行的数量countDistinct计算指定列中不同值的数量corr计算两个列之间的相关系数covar_pop计算两个列之间的总体协方差covar_samp计算两…