保姆级教程,终于搞懂脏读、幻读和不可重复读了!(经典回顾)
作者 | 王磊
来源 | Java中文社群(ID:javacn666)
转载请联系授权(微信ID:GG_Stone)
我的文章合集:https://gitee.com/mydb/interview
在 MySQL 中事务的隔离级别有以下 4 种:
读未提交(READ UNCOMMITTED)
读已提交(READ COMMITTED)
可重复读(REPEATABLE READ)
序列化(SERIALIZABLE)
MySQL 默认的事务隔离级别是可重复读(REPEATABLE READ),这 4 种隔离级别的说明如下。
1.READ UNCOMMITTED
读未提交,也叫未提交读,该隔离级别的事务可以看到其他事务中未提交的数据。该隔离级别因为可以读取到其他事务中未提交的数据,而未提交的数据可能会发生回滚,因此我们把该级别读取到的数据称之为脏数据,把这个问题称之为脏读。
2.READ COMMITTED
读已提交,也叫提交读,该隔离级别的事务能读取到已经提交事务的数据,因此它不会有脏读问题。但由于在事务的执行中可以读取到其他事务提交的结果,所以在不同时间的相同 SQL 查询中,可能会得到不同的结果,这种现象叫做不可重复读。
3.REPEATABLE READ
可重复读,是 MySQL 的默认事务隔离级别,它能确保同一事务多次查询的结果一致。但也会有新的问题,比如此级别的事务正在执行时,另一个事务成功的插入了某条数据,但因为它每次查询的结果都是一样的,所以会导致查询不到这条数据,自己重复插入时又失败(因为唯一约束的原因)。明明在事务中查询不到这条信息,但自己就是插入不进去,这就叫幻读 (Phantom Read)。
4.SERIALIZABLE
序列化,事务最高隔离级别,它会强制事务排序,使之不会发生冲突,从而解决了脏读、不可重复读和幻读问题,但因为执行效率低,所以真正使用的场景并不多。
简单总结一下,MySQL 的 4 种事务隔离级别对应脏读、不可重复读和幻读的关系如下:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITTED) | √ | √ | √ |
读已提交(READ COMMITTED) | × | √ | √ |
可重复读(REPEATABLE READ) | × | × | √ |
串行化(SERIALIZABLE) | × | × | × |
只看以上概念会比较抽象,接下来,咱们一步步通过执行的结果来理解这几种隔离级别的区别。
前置知识
1.事务相关的常用命令
# 查看 MySQL 版本
select version();# 开启事务
start transaction;# 提交事务
commit;# 回滚事务
rollback;
2.MySQL 8 之前查询事务的隔离级别
查看全局 MySQL 事务隔离级别和当前会话的事务隔离级别的 SQL 如下:
select @@global.tx_isolation,@@tx_isolation;
以上 SQL 执行结果如下图所示:
3.MySQL 8 之后查询事务的隔离级别
select @@global.transaction_isolation,@@transaction_isolation;
4.查看连接的客户端详情
每个 MySQL 命令行窗口就是一个 MySQL 客户端,每个客户端都可以单独设置(不同的)事务隔离级别,这也是演示 MySQL 并发事务的基础。以下是查询客户端连接的 SQL 命令:
show processlist;
以上 SQL 执行结果如下:
5.查询连接客户端的数量
可以使用以下 SQL 命令,查询连当前接 MySQL 服务器的客户端数量:
show status like 'Threads%';
以上 SQL 执行结果如下:
6.设置客户端的事务隔离级别
通过以下 SQL 可以设置当前客户端的事务隔离级别:
set session transaction isolation level 事务隔离级别;
事务隔离级别的值有 4 个:READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。
7.新建数据库和测试数据
创建测试数据库和表信息,执行 SQL 如下:
-- 创建数据库
drop database if exists testdb;
create database testdb;
use testdb;
-- 创建表
create table userinfo(id int primary key auto_increment,name varchar(250) not null,balance decimal(10,2) not null default 0
);
-- 插入测试数据
insert into userinfo(id,name,balance) values(1,'Java',100),(2,'MySQL',200);
创建的表结构和数据如下:
8.名称约定
接下来会使用两个窗口(两个客户端)来演示事务不同隔离级别中脏读、不可重复读和幻读的问题。其中左边的黑底绿字的客户端下文将使用“窗口 1”来指代,而右边的蓝底白字的客户端下文将用“窗口 2”来指代,如下图所示:
脏读
一个事务读到另外一个事务还没有提交的数据,称之为脏读。脏读演示的执行流程如下:
执行步骤 | 客户端1(窗口1) | 客户端2(窗口2) | 说明 |
---|---|---|---|
第 1 步 |
set session transaction isolation level read uncommitted; start transaction; select * from userinfo; |
设置事务隔离级别为读未提交; 开启事务; 查询用户列表,其中 Java 用户的余额为 100 元。 |
|
第 2 步 |
start transaction; update userinfo set balance=balance+50 where name='Java'; |
开启事务; 给 Java 用户的账户加 50 元; |
|
第 3 步 | select * from userinfo; | 查询用户列表,其中 Java 用户的余额变成了 150 元。 |
脏读演示步骤1
设置窗口 2 的事务隔离级别为读未提交,设置命令如下:
set session transaction isolation level read uncommitted;
PS:事务隔离级别读未提交存在脏读的问题。
然后使用命令来检查当前连接窗口的事务隔离界别,如下图所示:开启事务并查询用户列表信息,如下图所示:
脏读演示步骤2
在窗口 1 中开启一个事务,并给 Java 账户加 50 元,但不提交事务,执行的 SQL 如下:
脏读演示步骤3
在窗口 2 中再次查询用户列表,执行结果如下:从上述结果可以看出,在窗口 2 中读取到了窗口 1 中事务未提交的数据,这就是脏读。
不可重复读
不可重复读是指一个事务先后执行同一条 SQL,但两次读取到的数据不同,就是不可重复读。不可重复读演示的执行流程如下:
执行步骤 | 客户端1(窗口1) | 客户端2(窗口2) | 说明 |
---|---|---|---|
第 1 步 |
set session transaction isolation level read committed; start transaction; select * from userinfo; |
设置事务隔离级别为读已提交; 开启事务; 查询用户列表,其中 Java 用户的余额是 100 元。 |
|
第 2 步 | start transaction;update userinfo set balance=balance+20 where name='Java';commit; |
开启事务; 给 Java 用户的余额加 20 元;提交事务。 |
|
第 3 步 | select * from userinfo; | 查询用户列表,其中 Java 用户的余额变成了 120 元。 |
窗口 2 同一个事务中的两次查询,得到了不同的结果这就是不可重复读,具体执行步骤如下。
不可重复读演示步骤1
设置窗口 2 的事务隔离级别为读已提交,设置命令如下:
set session transaction isolation level read committed;
PS:读已提交可以解决脏读的问题,但存在不可重复读的问题。
使用命令来检查当前连接窗口的事务隔离界别,如下图所示:在窗口 2 中开启事务,并查询用户表,执行结果如下:此时查询的列表中,Java 用户的余额为 100 元。
不可重复读演示步骤2
在窗口 1 中开启事务,并给 Java 用户添加 20 元,但不提交事务,再观察窗口 2 中有没有脏读的问题,具体执行结果如下图所示:从上述结果可以看出,当把窗口的事务隔离级别设置为读已提交,已经不存在脏读问题了。接下来在窗口 1 中提交事务,执行结果如下图所示:
不可重复读演示步骤3
切换到窗口 2 中再次查询用户列表,执行结果如下:从上述结果可以看出,此时 Java 用户的余额已经变成 120 元了。在同一个事务中,先后查询的两次结果不一致就是不可重复读。
不可重复读和脏读的区别
脏读可以读到其他事务中未提交的数据,而不可重复读是读取到了其他事务已经提交的数据,但前后两次读取的结果不同。
幻读
幻读名如其文,它就像发生了某种幻觉一样,在一个事务中明明没有查到主键为 X 的数据,但主键为 X 的数据就是插入不进去,就像某种幻觉一样。幻读演示的执行流程如下:
执行步骤 | 客户端1(窗口1) | 客户端2(窗口2) | 说明 |
---|---|---|---|
第 1 步 |
set session transaction isolation level repeatable read; start transaction; select * from userinfo where id=3; |
设置事务隔离级别为可重复读; 开启事务; 查询用户编号为 3 的数据,查询结果为空。 |
|
第 2 步 |
start transaction; insert into userinfo(id,name,balance) values(3,'Spring',100); commit; |
开启事务; 添加用户,用户编号为 3; 提交事务。 |
|
第 3 步 | insert into userinfo(id,name,balance) values(3,'Spring',100); | 窗口 2 添加用户编号为 3 的数据,执行失败。 | |
第 4 步 | select * from userinfo where id=3; | 查询用户编号为 3 的数据,查询结果为空。 |
具体执行结果如下步骤所示。
幻读演示步骤1
设置窗口 2 为可重复读,可重复有幻读的问题,查询编号为 3 的用户,具体执行 SQL 如下:
set session transaction isolation level repeatable read;
start transaction;
select * from userinfo where id=3;
以上 SQL 执行结果如下图所示:从上述结果可以看出,查询的结果中 id=3 的数据为空。
幻读演示步骤2
开启窗口 1 的事务,插入用户编号为 3 的数据,然后成功提交事务,执行 SQL 如下:
start transaction;
insert into userinfo(id,name,balance) values(3,'Spring',100);
commit;
以上 SQL 执行结果如下图所示:
幻读演示步骤3
在窗口 2 中插入用户编号为 3 的数据,执行 SQL 如下:
insert into userinfo(id,name,balance) values(3,'Spring',100);
以上 SQL 执行结果如下图所示:添加用户数据失败,提示表中已经存在了编号为 3 的数据,且此字段为主键,不能添加多个。
幻读演示步骤4
在窗口 2 中,重新执行查询:
select * from userinfo where id=3;
以上 SQL 执行结果如下图所示:/ 在此事务中查询明明没有编号为 3 的用户,但插入的时候却却提示已经存在了,这就是幻读。
不可重复读和幻读的区别
二者描述的则重点不同,不可重复读描述的侧重点是修改操作,而幻读描述的侧重点是添加和删除操作。
总结
本文演示了 MySQL 的 4 种事务隔离级别:读未提交(有脏读问题)、读已提交(有不可重复读的问题)、可重复读(有幻读的问题)和序列化,其中可重复读是 MySQL 默认的事务隔离级别。脏读是读到了其他事务未提交的数据,而不可重复读是读到了其他事务已经提交的数据,但前后查询的结果不同,而幻读则是明明查询不到,但就是插入不了。
是非审之于己,毁誉听之于人,得失安之于数。
公众号:Java面试真题解析
保姆级教程,终于搞懂脏读、幻读和不可重复读了!(经典回顾)相关推荐
- 保姆级教程,终于搞懂脏读、幻读和不可重复读了!
作者 | 王磊 来源 | Java中文社群(ID:javacn666) 转载请联系授权(微信ID:GG_Stone) 我的文章合集:https://gitee.com/mydb/interview 在 ...
- 量化交易-利用同花顺量化平台supermind 5行代码搞定多条件选股并微信实时收消息-保姆级教程
利用supermind 5行代码搞定多条件选股并在微信实时收消息-保姆级教程 前言 对大部分炒股的朋友来说,日常最耗时的就是盯着选股条件然后不停的选股,我经常苦恼于有无程序能自动化实现选股,然后选中之 ...
- 判断是不是链接 正则_Python 正则表达式 保姆级教程,小学生都看得懂!!
~点击 蓝字 关注,获取更多资源~ 0 前言 上一篇文章,2020,还不会正则???,和小伙伴们一起学习了 Python 中的正则表达式,读完之后,总感觉少了点什么东西,无法尽兴?就好像爱你们的心少了 ...
- 从购买服务器到网站搭建成功保姆级教程~超详细
??从购买服务器到网站搭建成功保姆级教程~真的超详细,各位看官细品 ??前言 ??预备知识 ??什么是云服务器? ??什么是域名? ??什么是SSL证书? ??服务器选配 ??阿里云[官网链接](ht ...
- js对象、数组、字符串操作总结(保姆级教程)
对象操作 1. 扩展运算符 作用是遍历某个对象或者数组 testMethod() {// 三个点 ... 俗称扩展运算符或延展运算符,需要注意的是扩展运算符在拷贝的时候只能深拷贝第一层,第二层及以下都 ...
- ❤️周末爆肝两天❤️,万字长文,手把手教你配置CSDN主页的独特域名(保姆级教程,建议收藏)
❤️ 感受下效果图 ❤️ 目录 一.前言 二.先解决有没有的问题 1. 前置条件 2. 购买云服务器 3. 购买DNS域名 4. 配置Apache2服务 5. 配置云服务器的端口映射 6. 配置ngi ...
- 金融数据获取:当爬虫遇上要鼠标滚轮滚动才会刷新数据的网页(保姆级教程)
目录 1. 谁这么会给我整活儿 2. Selenium模拟网页浏览器爬取 2.1 安装和准备工作 2.2.1 高度判断 2.2.2 顶部距离判断 3: 爬取内容 4: 完整代码,结果展示 1. 谁这么 ...
- 快速上手Springboot项目(登录注册保姆级教程)
本文章对SpringBoot开发后端项目结构做了简单介绍,并示范了使用SpringBoot+MySQL实现登录的后端功能,与本博客的另一篇文章 Vue 实现登录注册功能(前后端分离完整案例) | Ma ...
- 天才少年稚晖君 | 【保姆级教程】个人深度学习工作站配置指南
天才少年稚晖君 | [保姆级教程]个人深度学习工作站配置指南 来源:https://zhuanlan.zhihu.com/p/336429888 0. 前言 工作原因一直想配置一台自己的深度学习工作站 ...
最新文章
- 3.5 Facade(外观)
- 浏览器崩溃_字节跳动程序员28岁身价上亿,财务自由宣布退休;微软最新系统再迎“喜报”:更多用户的浏览器开始崩溃...
- 世界手机号码格式_世界上手机号码最长的国家是中国,最短的是哪个国家?
- Elasticsearch(二) ik分词器的安装 以及 自定义分词
- 2018深圳国际零售信息化暨无人售货展
- 032. asp.netWeb用户控件之一初识用户控件并为其自定义属性
- MFC动态菜单全攻略
- 计算机视觉知识点-车型识别
- Egoist (罪恶王冠) | mmd动作+镜头下载
- 牛牛现在有n张扑克牌-字符串
- cf 581A— Vasya the Hipster
- 高考志愿填报|物联网为何成为【热门选手】?
- PyBullet快速上手教程
- C# 正则表达式 Regex类的使用
- android模拟器 对比,安卓模拟器多开用哪个模拟器好?实测数据对比哪个好用
- SQL Server查询排序 升序 降序
- leetcode 179 最大数
- 拿到项目的 基本流程
- C# 读取txt文件生成Word文档
- 视频号匹配时事热点创作内容效果更好