深度解析 MySQL 事务、隔离级别和 MVCC 机制:构建高效并发的数据交响乐(三)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: 深度解析 MySQL 事务、隔离级别和 MVCC 机制:构建高效并发的数据交响乐(三)

不可重复读问题

基于上面的操作,先将 trx_id = 100 事务提交,然后再到 trx_id =120 事务更新表中 technology_column.id 为 1 的记录,也就是执行如下 SQL 语句:

Trx_id-100:Commit;
Trx_id-120:update technology_column set category_name ='分布式' where id = 1;   
Trx_id-120:update technology_column set category_name ='Linux' where id = 1; 

此时,technology_column 表中 id=1 记录的版本链如下图所示:

接着使用 READ COMMITTED 隔离级别事务,在 Trx_id-120 begin; 语句后查询的数据为 Spring,而在 Trx_id-100:Commit; 语句执行之后,Trx_id-120:Commit; 语句执行之前,使用 Trx_id 为 120 的事务,查询 id=1 记录,此时查询到的数据为 Redis,整个的执行过程如下:

  1. 在执行 SELECT 语句时又会单独生成一个 ReadView
  2. ReadView 活跃集合 m_ids 内容为 120,min_trx_id 为 120,max_trx_id 为 121,creator_id 为 0

Trx_id 为 100 的事务已经提交了,所以再次生成 ReadView 快照时就没有它了

  1. 接着从版本链中挑选可见的记录,从图中可以看出,最新版本的 category_name 内容为 Linux,该版本的 Trx_id 值为 120,在 m_ids 集合内,所以不符合可见性要求
  2. 通过 Roll_ptr 指针跳到下一个版本中,下一个版本 category_name 内容为 分布式,该版本的 Trx_id 值为 120,在 m_ids 集合内,也不符合可见性要求
  3. 通过 Roll_ptr 指针跳到下一个版本中,下一个版本 category_name 内容为 Redis,该版本的 Trx_id 为 100,小于 ReadView 中 min_trx_id 值 120,所以这个版本的内容是符合可见性要求的,最后返回给用户的版本就是这条 category_name 内容为 Redis 的数据
  4. 此时,在 Trx_id 为 120 的事务执行期间,就发生了两次查询数据不一致的问题,这就是不可重复读问题,由此说明,READ COMMITTED 读已提交隔离级别事务避免不了此问题的发生

依此类推,若之后 Trx_id 为 120 的事务也提交了,再次使用 READ COMMITTED 隔离级别事务,查询 technology_column.id 为 1 的记录时,得到的结果就是 Linux,具体流程类似于上面

通过 m_ids(活跃事务集合)、min_trx_id(最小事务 id) 结合隔离级别的特性,来比对版本链的记录是否符合可见性要求,而读已提交是在每次执行 SELECT 语句时都会生成 ReadView 视图快照.

REPEATABLE READ

REPEATABLE READ 隔离级别的事务只有在第一次读取数据时才会生成一个 ReadView,之后的查询就不会重复生成了

比如:现有系统中有两个事务 > Trx_id 100、120 在执行,事务 > Trx_id 100、120 SQL 语句如下:

Trx_id-100、120 Begin;
Trx_id-100、120:select * from technology_column where id = 1;
Trx_id-100:update technology_column set category_name ='MySQL' where id = 1;   
Trx_id-100:update technology_column set category_name ='Redis' where id = 1;   
Trx_id-100:select * from technology_column where id = 1;
Trx_id-100:Commit;
Trx_id-120:update technology_column set category_name ='分布式' where id = 1;   
Trx_id-120:update technology_column set category_name ='Linux' where id = 1;   
Trx_id-120:select * from technology_column where id = 1;
Trx_id-120:Commit;

不可重复读问题

在事务 100、120 执行前都会先生成 ReadView 读取视图,也就是它们读取到的 category_name 内容都是 Spring,在前面介绍过,READ COMMITTED 读已提交隔离级别会在 Trx_id 为 120 的事务执行期间发生不可重复读问题,所以在这里主要分析 REPEATABLE READ 可重复读隔离级别是如何解决此问题的, 它在执行事务时对应的版本链表如下:

Trx_id-120

在 Trx_id 为 120 第一次 SELECT 语句执行以后,生成了 ReadView 读取视图快照

ReadView 内容:m_ids 活跃事务集合为 100、120,min_trx_id 为 121,creator_trx_id 为 0

因为当前事务隔离级别为 REPEATABLE READ,所以在 Trx_id 为 120 的事务执行期间会直接复用上面所生成的 ReadView 快照信息,也就是前后两次 SELECT 查询语句执行后的结果都是一致的,读取到的 category_name 内容都是 Spring,这就是可重复读解决不可重复读问题的核心所在

因为它在事务执行期间,一直都是使用的第一次生成的 ReadView,自然而然也不会发生脏读问题,不会读取到其他事务已提交的数据信息

总结一下 ReadView 比较规则,如下:

  1. 若被访问版本的 Trx_id 属性值与 ReadView 中 creator_trx_id 值相同,那么就意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务所访问
  2. 若被访问版本的 Trx_id 属性值小于 ReadView 中 min_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 之前已经提交,所以该版本可以被当前事务所访问
  3. 若被访问的版本 Trx_id 属性值大于或等于 ReadView 中 max_trx_id 值,表明生成该版本的事务在当前事务生成 ReadView 之后才开启,所以该版本不可以被当前事务所访问
  4. 若被访问版本的 Trx_id 属性值在 ReadView 中 min_trx_id、max_trx_id 之间(min_trx_id < trx_id < max_trx_id)那就需要判断一下 trx_id 属性值是否在 m_ids 活跃事务集合中;如果在的话,说明创建 ReadView 时生成该版本的事务还是活跃的,该版本不可以被其他事务所访问;如果不在的话,说明创建 ReadView 时生成该版本的事务已经被提交,该版本可以被其他事务所访问

幻读问题

REPEATABLE READ 隔离级别在 MVCC 机制下可以解决不可重复读问题,也可以在一定程度下解决幻读问题

幻读问题:一个事务在某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录,而这个记录来自于另外一个事务添加的新记录

比如:在 REPEATABLE READ 隔离级别下,事务 T1 先通过某个搜索条件读取到多条记录,然后事务 T2 插入一条符合这个搜索条件的记录并提交完成,此时 T1 再次通过这个搜索条件执行查询,结果如下:无论事务 T2 比 事务 T1 是否先开启,事务 T1 都是看不到 T2 的提交信息的,因为在 REPEATABLE READ 隔离级别下,只会在第一次查询时才生成 ReadView 读取视图快照,后续的查询会延续使用第一次生成的 ReadView 信息,由此可见,REPEATABLE READ 可以在一定程序下解决幻读问题的发生

但在某些情况下,REPEATABLE READ 隔离级别,InnoDB MVCC 会发生幻读问题,结合案例如下:

首先在事务 T1 中执行查询语句,如下:

select * from technology_column where id between 2 and 3;

此时 technology_column 表中只有一条数据,id 为 2、3 的数据并未存在;接着我们在事务 T2 中执行 INSERT 语句,如下图:

通过以上语句,在 technology_column 表中插入了一条 id 为 2 的数据,此时,再回到事务 T1 执行如下语句:

update technology_column set category_name = 'RocketMQ' where id = 2;
select * from technology_column where id between 2 and 3;

执行结果如下:

很明显事务 T1 出现了幻读现象,在 REPEATABLE READ 隔离级别下,事务 T1 第一次执行普通的 SELECT 语句时生成了一个 ReadView(但此时版本链表中没有生成的对应的条目)之后 T2 事务向表中新插入一条记录并提交,然后 T1 事务执行了 Update 语句;ReadView 并不能阻止事务 T1 执行 UPDATE 或 DELETE 语句来改动这个新插入的记录,但这样一来,这条新记录的 Trx_id 隐藏列的值就变成了事务 T1 的事务 id

之后事务 T1 再次使用普通 SELECT 语句去查询这条记录时就可以看到了,也可以把这条记录返回给客户端,因为这种特殊现象的存在,所以 REPEATABLE READ 可重复隔离级别不能完全避免幻读问题的发生

事务 T1 第一次读是空的情况,事务 T2 新增了这条数据,事务 T1 在自己事务中对这条数据进行了修改

小结

所谓的 MVCC(Multi-Version Concurrency Control)多版本并发控制,指的就是在使用 READ COMMITTED、REPEATABLE READ 这两种隔离级别事务时,执行普通 SELECT 操作时访问数据的版本链过程,这样子可以使不同的事务读写、写读操作并发执行,从而提高系统的性能

READ COMMITTED、REPEATABLE READ 这两种隔离级别事务一个最大不同:生成 ReadView 时机不同,READ COMMITTED 在每次进行普通 SELECT 操作时都会生成一个 ReadView,而 REPEATABLE READ 只有在第一次进行普通 SELECT 操作时生成一个 ReadView,之后的查询操作都重复使用这个 ReadView 即可,从而基本上可以避免幻读问题的发生,但如果第一次读 ReadView 是空数据的情况下 > 某些场景则无法避免幻读的发生

最后,所谓的 MVCC 只是在我们进行普通 SELECT 查询时才生效,对于锁定读就是不普通的查询,所以它就无法让我们的 MVCC 机制发挥作用了.

总结

该篇博文,简而言之说明了 MySQL 中事务四大特性,详细阐述了由于四种隔离级别下各自会产生什么样的问题,从实战方面进行演示了 MySQL 基于事务上的一些基本操作,最重要的是讲述了 InnoDB 引擎下的 MVCC 机制,有提及它的版本链、ReadView 以及其下一些比较重要的概念,最后,重点说明了 READ COMMITTED 读已提交、REPEATABLE READ 可重复读,这两种隔离级别会是如何解决一些并发问题以及会在什么样的场景下发生一些异常问题。希望您能够喜欢,能帮助到你是我最大的快乐!

另外,博主有专门讲解 Spring 事务是如何结合 MySQL 一起应用的,博文链接如下:

Spring 事务传播机制、隔离级别以及事务执行流程源码结合案例分析

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

推荐专栏:Spring、MySQL,订阅一波不再迷路

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!


相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
11天前
|
NoSQL Java Linux
《docker高级篇(大厂进阶):2.DockerFile解析》包括:是什么、DockerFile构建过程解析、DockerFile常用保留字指令、案例、小总结
《docker高级篇(大厂进阶):2.DockerFile解析》包括:是什么、DockerFile构建过程解析、DockerFile常用保留字指令、案例、小总结
164 75
|
9天前
|
SQL 安全 关系型数据库
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
事务是MySQL中一组不可分割的操作集合,确保所有操作要么全部成功,要么全部失败。本文利用SQL演示并总结了事务操作、事务四大特性、并发事务问题、事务隔离级别。
【MySQL基础篇】事务(事务操作、事务四大特性、并发事务问题、事务隔离级别)
|
2月前
|
自然语言处理 算法 Python
再谈递归下降解析器:构建一个简单的算术表达式解析器
本文介绍了递归下降解析器的原理与实现,重点讲解了如何使用Python构建一个简单的算术表达式解析器。通过定义文法、实现词法分析器和解析器类,最终实现了对基本算术表达式的解析与计算功能。
101 52
|
15天前
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(04)事务隔离级别、AICD、CAP、BASE原则一直搞不懂? | 看这篇就够了
本文详细介绍了数据库事务的四大特性(AICD原则),包括原子性、隔离性、一致性和持久性,并深入探讨了事务并发问题与隔离级别。同时,文章还讲解了分布式系统中的CAP理论及其不可能三角关系,以及BASE原则在分布式系统设计中的应用。通过具体案例和图解,帮助读者理解事务处理的核心概念和最佳实践,为应对相关技术面试提供了全面的知识准备。
|
20天前
|
存储 关系型数据库 MySQL
mysql怎么查询longblob类型数据的大小
通过本文的介绍,希望您能深入理解如何查询MySQL中 `LONG BLOB`类型数据的大小,并结合优化技术提升查询性能,以满足实际业务需求。
84 6
|
2月前
|
弹性计算 持续交付 API
构建高效后端服务:微服务架构的深度解析与实践
在当今快速发展的软件行业中,构建高效、可扩展且易于维护的后端服务是每个技术团队的追求。本文将深入探讨微服务架构的核心概念、设计原则及其在实际项目中的应用,通过具体案例分析,展示如何利用微服务架构解决传统单体应用面临的挑战,提升系统的灵活性和响应速度。我们将从微服务的拆分策略、通信机制、服务发现、配置管理、以及持续集成/持续部署(CI/CD)等方面进行全面剖析,旨在为读者提供一套实用的微服务实施指南。
|
1月前
|
SQL 关系型数据库 MySQL
mysql分页读取数据重复问题
在服务端开发中,与MySQL数据库进行数据交互时,常因数据量大、网络延迟等因素需分页读取数据。文章介绍了使用`limit`和`offset`参数实现分页的方法,并针对分页过程中可能出现的数据重复问题进行了详细分析,提出了利用时间戳或确保排序规则绝对性等解决方案。
|
2月前
|
关系型数据库 MySQL 数据库
GBase 数据库如何像MYSQL一样存放多行数据
GBase 数据库如何像MYSQL一样存放多行数据
|
2月前
|
关系型数据库 MySQL 数据库
MySQL事务隔离级别及默认隔离级别的设置
在数据库系统中,事务隔离级别是一个关键的概念,它决定了事务在并发执行时如何相互隔离。MySQL提供了四种事务隔离级别,每种级别都解决了不同的并发问题。本文将详细介绍这些隔离级别以及MySQL的默认隔离级别。
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2

推荐镜像

更多