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

本文涉及的产品
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介: 2024年小结:感谢阿里云开发者社区每月的分享交流活动,支持持续学习和进步。过去五个月投稿29篇,其中17篇获高分认可。本文详细介绍了MySQL InnoDB存储引擎的MVCC机制,包括数据版本链、readView视图及解决脏读、不可重复读、幻读问题的demo演示。

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

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

近5个月以来投稿并不多,仅29篇。好在其中超过一半,多达有17篇得到平台认可,认定为高分内容。2014是收获的一年。感恩感谢!新的一年,争取有更多时间,和大家交流学习分享,包括家庭、日常、职场其他非技术性内容。


一、前言背景

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

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

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

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

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

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

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


期待可以写一篇2024总结,聊聊日常生活、职场等非技术内容。


一、前言背景


    之前系列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并发编程的锁机制类似。

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


推荐阅读拉丁解牛相关专题系列(欢迎交流讨论,搜索:拉丁解牛):

1、JVM进阶调优系列(3)堆内存的对象什么时候被回收?

2、JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?

3、JVM进阶调优系列(1)类加载器原理一文讲透

4、JAVA并发编程系列(13)Future、FutureTask异步小王子

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
7月前
|
存储 SQL 关系型数据库
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
mysql底层原理:索引、慢查询、 sql优化、事务、隔离级别、MVCC、redolog、undolog(图解+秒懂+史上最全)
|
11月前
|
自然语言处理 搜索推荐 关系型数据库
MySQL实现文档全文搜索,分词匹配多段落重排展示,知识库搜索原理分享
本文介绍了在文档管理系统中实现高效全文搜索的方案。为解决原有ES搜索引擎私有化部署复杂、运维成本高的问题,我们转而使用MySQL实现搜索功能。通过对用户输入预处理、数据库模糊匹配、结果分段与关键字标红等步骤,实现了精准且高效的搜索效果。目前方案适用于中小企业,未来将根据需求优化并可能重新引入专业搜索引擎以提升性能。
522 5
|
6月前
|
存储 SQL 关系型数据库
MySQL中binlog、redolog与undolog的不同之处解析
每个都扮演回答回溯与错误修正机构角色: BinLog像历史记载员详细记载每件大大小小事件; RedoLog则像紧急救援队伍遇见突發情況追踪最后活动轨迹尽力补救; UndoLog就类似时间机器可倒带历史让一切归位原始样貌同时兼具平行宇宙观察能让多人同时看见各自期望看见历程而互不干扰.
325 9
|
7月前
|
SQL 关系型数据库 MySQL
MySQL group by 底层原理详解。group by 执行 慢 原因深度分析。(图解+秒懂+史上最全)
MySQL group by 底层原理详解。group by 执行 慢 原因深度分析。(图解+秒懂+史上最全)
MySQL group by 底层原理详解。group by 执行 慢 原因深度分析。(图解+秒懂+史上最全)
|
9月前
|
SQL 监控 关系型数据库
MySQL日志分析:binlog、redolog、undolog三大日志的深度探讨。
数据库管理其实和写小说一样,需要规划,需要修订,也需要有能力回滚。理解这些日志的作用与优化,就像把握写作工具的使用与运用,为我们的数据库保驾护航。
404 23
|
12月前
|
关系型数据库 MySQL 数据库
RDS用多了,你还知道MySQL主从复制底层原理和实现方案吗?
随着数据量增长和业务扩展,单个数据库难以满足需求,需调整为集群模式以实现负载均衡和读写分离。MySQL主从复制是常见的高可用架构,通过binlog日志同步数据,确保主从数据一致性。本文详细介绍MySQL主从复制原理及配置步骤,包括一主二从集群的搭建过程,帮助读者实现稳定可靠的数据库高可用架构。
727 9
RDS用多了,你还知道MySQL主从复制底层原理和实现方案吗?
|
12月前
|
SQL 关系型数据库 MySQL
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
1.请解释什么是MVCC,它在数据库中的作用是什么? 2.在MySQL中,MVCC是如何实现的?请简述其工作原理。 3.MVCC是如何解决读-写和写-写冲突的? 4.在并发环境中,当多个事务同时读取同一行数据时,MVCC是如何保证每个事务看到的数据版本是一致的? 5.MVCC如何帮助提高数据库的并发性能?
京东面试:MySQL MVCC是如何实现的?如何通过MVCC实现读已提交、可重复读隔离级别的?
|
12月前
|
存储 缓存 关系型数据库
MySQL进阶突击系列(08)年少不知BufferPool核心原理 | 大哥送来三条大金链子LRU、Flush、Free
本文深入探讨了MySQL中InnoDB存储引擎的buffer pool机制,包括其内存管理、数据页加载与淘汰策略。Buffer pool作为高并发读写的缓存池,默认大小为128MB,通过free链表、flush链表和LRU链表管理数据页的存取与淘汰。其中,改进型LRU链表采用冷热分离设计,确保预读机制不会影响缓存公平性。文章还介绍了缓存数据页的刷盘机制及参数配置,帮助读者理解buffer pool的运行原理,优化MySQL性能。
|
5月前
|
缓存 关系型数据库 BI
使用MYSQL Report分析数据库性能(下)
使用MYSQL Report分析数据库性能
430 158
|
5月前
|
关系型数据库 MySQL 数据库
自建数据库如何迁移至RDS MySQL实例
数据库迁移是一项复杂且耗时的工程,需考虑数据安全、完整性及业务中断影响。使用阿里云数据传输服务DTS,可快速、平滑完成迁移任务,将应用停机时间降至分钟级。您还可通过全量备份自建数据库并恢复至RDS MySQL实例,实现间接迁移上云。

相关产品

  • 云数据库 RDS MySQL 版
  • 推荐镜像

    更多