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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
云数据库 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,订阅一波不再迷路

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


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
19天前
|
存储 关系型数据库 MySQL
MySQL MVCC全面解读:掌握并发控制的核心机制
【10月更文挑战第15天】 在数据库管理系统中,MySQL的InnoDB存储引擎采用了一种称为MVCC(Multi-Version Concurrency Control,多版本并发控制)的技术来处理事务的并发访问。MVCC不仅提高了数据库的并发性能,还保证了事务的隔离性。本文将深入探讨MySQL中的MVCC机制,为你在面试中遇到的相关问题提供全面的解答。
55 2
|
6天前
|
监控 关系型数据库 MySQL
MySQL自增ID耗尽应对策略:技术解决方案全解析
在数据库管理中,MySQL的自增ID(AUTO_INCREMENT)属性为表中的每一行提供了一个唯一的标识符。然而,当自增ID达到其最大值时,如何处理这一情况成为了数据库管理员和开发者必须面对的问题。本文将探讨MySQL自增ID耗尽的原因、影响以及有效的应对策略。
21 3
|
7天前
|
存储 关系型数据库 MySQL
MySQL 字段类型深度解析:VARCHAR(50) 与 VARCHAR(500) 的差异
在MySQL数据库中,`VARCHAR`类型是一种非常灵活的字符串存储类型,它允许存储可变长度的字符串。然而,`VARCHAR(50)`和`VARCHAR(500)`之间的差异不仅仅是长度的不同,它们在存储效率、性能和使用场景上也有所不同。本文将深入探讨这两种字段类型的区别及其对数据库设计的影响。
17 2
|
11天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
11天前
|
存储 关系型数据库 MySQL
PHP与MySQL动态网站开发深度解析####
本文作为技术性文章,深入探讨了PHP与MySQL结合在动态网站开发中的应用实践,从环境搭建到具体案例实现,旨在为开发者提供一套详尽的实战指南。不同于常规摘要仅概述内容,本文将以“手把手”的教学方式,引导读者逐步构建一个功能完备的动态网站,涵盖前端用户界面设计、后端逻辑处理及数据库高效管理等关键环节,确保读者能够全面掌握PHP与MySQL在动态网站开发中的精髓。 ####
|
11天前
|
存储 分布式计算 Java
存算分离与计算向数据移动:深度解析与Java实现
【11月更文挑战第10天】随着大数据时代的到来,数据量的激增给传统的数据处理架构带来了巨大的挑战。传统的“存算一体”架构,即计算资源与存储资源紧密耦合,在处理海量数据时逐渐显露出其局限性。为了应对这些挑战,存算分离(Disaggregated Storage and Compute Architecture)和计算向数据移动(Compute Moves to Data)两种架构应运而生,成为大数据处理领域的热门技术。
32 2
|
17天前
|
JavaScript API 开发工具
<大厂实战场景> ~ Flutter&鸿蒙next 解析后端返回的 HTML 数据详解
本文介绍了如何在 Flutter 中解析后端返回的 HTML 数据。首先解释了 HTML 解析的概念,然后详细介绍了使用 `http` 和 `html` 库的步骤,包括添加依赖、获取 HTML 数据、解析 HTML 内容和在 Flutter UI 中显示解析结果。通过具体的代码示例,展示了如何从 URL 获取 HTML 并提取特定信息,如链接列表。希望本文能帮助你在 Flutter 应用中更好地处理 HTML 数据。
99 1
|
17天前
|
JSON 前端开发 JavaScript
API接口商品详情接口数据解析
商品详情接口通常用于提供特定商品的详细信息,这些信息比商品列表接口中的信息更加详细和全面。以下是一个示例的JSON数据格式,用于表示一个商品详情API接口的响应。这个示例假定API返回一个包含商品详细信息的对象。
|
8天前
|
SQL 关系型数据库 MySQL
go语言数据库中mysql驱动安装
【11月更文挑战第2天】
22 4
|
6天前
|
SQL 关系型数据库 MySQL
12 PHP配置数据库MySQL
路老师分享了PHP操作MySQL数据库的方法,包括安装并连接MySQL服务器、选择数据库、执行SQL语句(如插入、更新、删除和查询),以及将结果集返回到数组。通过具体示例代码,详细介绍了每一步的操作流程,帮助读者快速入门PHP与MySQL的交互。
19 1

推荐镜像

更多