MySQL是如何解决不可重复读隔离级别中的幻读问题的
怎么入门数据库编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《MySQL是如何解决不可重复读隔离级别中的幻读问题的》,涉及到MySQL、学习笔记,有需要的可以收藏一下
大部分数据库系统(如Oracle)都将都将读提交(Read-Commited)作为默认隔离级别,而MySQL却选择可重复读(Repeatable-Read)作为其默认隔离级别。这篇文章我们就分析下MySQL为何会选取不可重复读隔离级别作为默认隔离机制以及是如何解决不可重复读隔离级别的幻读问题。
隔离级别
展开分析之前,我们先来认识下隔离级别的概念。
隔离级别总共有四种:
- 读未提交(Read-Uncommited)
- 读提交(Read-Commited)
- 可重复读(Repeatable-Read)
- 串行化(Serializable)
其中,读未提交和串行化隔离级别一般不会使用到,因为读未提交会导致脏读,不可重复读,幻读等一系列问题。而串行化是将所有的事务强制串行执行,严重影响并发性能。
这里我们简单介绍下脏读,不可重复读和幻读的概念:
- 脏读:事务A读取到了事务B修改但未提交且最后要回滚的数据。
如上图所示,t3时刻,事务A读取到了事务B还累加但是还未提交的a值,且在t3时刻,事务B回滚了,那么事务A基于t3时刻的查询所做的操作就会出现问题。
- 不可重复读:事务A前后读取到的数据不一致。
如上图所示,事务A在t2时刻读取到a的值,和t4时刻读取到的a的值不一致,因为事务B在t3时刻对a值进行了更新并提交。
- 幻读:事务A前后读取的结果条数不一致。
如上图所示,事务A在t2时刻和t4时刻获取到的数据条数不一致,因为事务B在t3时刻新增了一条符合事务A查询条件的数据并提交了。
其中,读提交(RC)隔离级别可以避免脏读的产生,但是会有不可重复读和幻读的问题;可重复读(RR)隔离级别可以避免脏读和不可重复读的问题,但是会有幻读的问题。

从binlog说起
关于MySQL为何会采用可重复读作为其默认隔离级别,得从MySQL的binlog说起。
binlog是MySQL的二进制日志,其记录数据表结构变更(alter,create)以及表数据更改(update,delete,insert)。
binlog日志有三种记录模式并各有优缺点:

MySQL默认的binlog记录模式为row。
在早期版本的MySQL中,binlog只有statement这一种记录模式,而此种模式导致的一个致命问题就是,在读提交(RC)隔离级别下会导致主从数据不一致。
在binlog中,记录日志的规则为:事务commit之后,记录日志。
我们看下在RC隔离机制下的一个案例:

假设此时binlog记录模式为statement。
那么记录binlog的顺序为:
t4时刻,记录t1表的delete语句;
t6时刻,记录对t表的update语句。
之后主从同步,master将自身的binlog同步给slave,slave执行同步时就会遇到的一个问题:slave会先执行删除t1表的内容,再执行更新t表的记录,此时会导致主从不一致。
接下来,我们在看下在RR隔离机制下的相同案例:

在RR隔离机制下,事务B的操作被阻塞,所以不会使得binlog在statement模式下记录顺序出现颠倒而导致主从数据不一致问题。
所以,由于早期MySQL版本中binlog只有statement模式,而在读提交(RC)隔离级别下记录的binlog使用statement模式会导致主从数据不一致的问题,所以,MySQL选择使用可重复读(RR)作为默认隔离级别以保证主从复制数据一致性。
MVCC
避免幻读
在高性能MySQL第三版中可重复读隔离级别的描述中写到:可重复读不能避免幻读的产生。幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新行,当之前的事务再次读取该范围的记录时,会产生幻行。InnoDB存储引擎通过多版本并发控制(MVCC)解决了幻读问题。
我们先来看一个可重复读隔离级别(RR)下的实例:

分析以上实例:
理论上,在t3时刻,事务B插入了一条符合事务A查询条件的记录并提交了事务,那么事务A在t2和t4时刻的查询应该是不一样的,但是实际结果确是:事务A前后查询结果一致。
实际上,这是MVCC的功劳。MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,在某个时间点事务开启时,其看到的数据是该时间点之前已经提交的数据的快照内容,这就保证了事务执行期间看到的数据时一致的。
分析“RR级别避免幻读”图示中的事务:
事务A在t2时刻获取到快照a,此快照将持续到t4时刻事务A提交事务。
事务B在t3时刻插入一条数据,但是事务A的快照a是基于t2时刻的快照,所以事务A并不能获取到事务B插入的数据。
快照
当然,MySQL的MVCC快照并不是每一个事务进来就copy一份数据库信息,而是基于数据表每行信息后面保存的系统版本号去实现的。如下图所示,一行信息会有多个版本并存,每个事务可能读取到的版本不一样。

每开启一个新的事务,系统版本都会自动递增,事务开始时刻的系统版本号会作为事务版本号,用来和查询到的每行记录的版本号进行比较。
针对select,insert,delete,update操作,InnoDB的MVCC具体操作为:
select:
InnoDB会根据两个条件检查每行记录值:
1、InnoDB只查找行的系统版本号小于或等于事务的系统版本号的记录,这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
2、行的删除版本要么未定义,要么大于当前事务版本号,确保可以读取到未删除之前的数据。
insert:
InnoDB为新插入的行保存当前系统版本号作为行版本号。
delete:
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
update:
InnoDB为插入的一行新记录保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。
文字太抽象,我们通过图来了解下:
start transaction with consistent snapshot;表示立即开启事务。
1、select
假设表T(id,a,b) 有数据[1,1,1],[2,2,2],[3,3,3]
假设当前事务ID为10:

如上,事务B之所以获取不到事务A的insert,是因为事务B的事务ID比事务A提交的插入数据的行标志ID小。
2、update
update的常规commit和select类似,我们看下未提交的情况。
假设表T(id,a,b) 有数据[1,1,1],[2,2,2],[3,3,3]
假设当前事务ID为10,当前id=1行版本号为10:

我们根据上图分析下流程。
首先为了方便理解,我们将行版本ID以及当前行记录内容记为x{z,z,z}
那么初始版本为:10{1,1,1}
事务A:可读版本为10,11
事务B:可读版本为10,11,12
事务C:可读版本为10,11,12,13
执行流程:
a、事务A,B,C依次开启事务;
b、事务C首先update并commit,那么此时版本为13{1,2,1};
c、接下来事务B执行update,此时查询到当前最新行版本为13{1,2,1},update需要当前读的数据,以防数据不一致,所以拿到13{1,2,1}版本数据进行update,此时版本变更为12{1,3,1};
d、然后事务A执行了select,查询时,因为事务A的事务版本号为11,所以只能读取行版本号小于等于11的版本,所以还是原始数据。
整个版本变更过程为:
10{1,1,1} -> 13{1,2,1} -> 12{1,3,1}
以上就是update的一种情况。
delete就和select,update类似,就不再详细说明了。
总结
MySQL之所以选择可重读事务隔离机制是因为早期binlog只支持statement格式,而此种格式在读提交隔离机制下回导致主从不一致。
MySQL的可重读隔离机制解决幻读的问题关键是靠MVCC的实现,事务ID和行版本ID保证了读取的一致性和隔离性。
在MySQL中,通过多版本并发控制(MVCC)去避免幻读的问题,但是只是在select的时候可以避免幻读,update之后再select还是可能会出现幻读现象。
今天关于《MySQL是如何解决不可重复读隔离级别中的幻读问题的》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
全网最全的用户--权限--角色 关系数据库设计
- 上一篇
- 全网最全的用户--权限--角色 关系数据库设计
- 下一篇
- 测试工程师进阶必备的六大能力! 你缺少哪个?
-
- 数据库 · MySQL | 2小时前 |
- MySQL invisible index 如何安全观察索引下线影响
- 300浏览 收藏
-
- 数据库 · MySQL | 3小时前 | MySQL · 索引优化 · generated column · mysql 索引 优化器 生成列
- MySQL 生成列索引为什么没有被优化器使用
- 300浏览 收藏
-
- 数据库 · MySQL | 6小时前 |
- MySQL 窗口函数取每组最新记录时如何处理并列时间
- 242浏览 收藏
-
- 数据库 · MySQL | 7小时前 |
- MySQL JSON_VALUE 返回 NULL 时怎么区分缺少路径和空值
- 170浏览 收藏
-
- 数据库 · MySQL | 9小时前 | MySQL · JSON查询 · JSON_TABLE · SQL技巧 · mysql JSON_TABLE FOR ORDINALITY JSON数组序号
- MySQL JSON_TABLE 的 FOR ORDINALITY 怎么保留数组原始序号
- 139浏览 收藏
-
- 数据库 · MySQL | 11小时前 |
- MySQL 复制延迟升高时怎么区分 SQL 线程和 IO 线程
- 304浏览 收藏
-
- 数据库 · MySQL | 13小时前 | MySQL事件 · 事件调度器 · 任务表排查 · mysql 定时任务 CREATE EVENT Event Scheduler INFORMATION_SCHEMA.EVENTS
- MySQL 事件调度器执行了但任务表没有更新怎么排查
- 486浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 47次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 198次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 133次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 67次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 47次使用
-
- MySQL 明明加了索引,为什么查询还是很慢?先查这 6 个点
- 2026-06-27 374浏览
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- golang 基于 mysql 简单实现分布式读写锁
- 2023-01-07 384浏览
-
- 详解如何利用GORM实现MySQL事务
- 2023-01-07 184浏览
-
- Go语言实现操作MySQL的基础知识总结
- 2023-01-23 265浏览



