MySQL环境下的脏读、不可重复读与幻读
《高性能MySQL》第三版在章节1.3提到了脏读,不可重复读,幻读三个概念(中文版第9页),如下表所示,但是翻了下索引,却发现没有对这三个概念的解释,而这三个概念却是面试热点问题之一。本文通过一些实验案例来说明什么是脏读,不可重复读以及幻读。
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
READ UNCOMMITTED | Yes | Yes | Yes | No |
READ COMMITTED | No | Yes | Yes | No |
REPEATABLE READ | No | No | Yes | No |
SERIALIZABLE | No | No | No | Yes |
实验环境及准备
环境
- MySQL版本:5.7.28
- 数据库:sakila,这个是MySQL官网提供的数据库样例
- 数据表:actor
准备
在接下来的实验里,我们将开启两个MySQL命令行窗口,所以我们需要先做好一些全局设置,主要包括隔离级别的设置和事务提交模式的设置。
- 设置隔离级别
MySQL默认的隔离级别为REPEATABLE READ(可重复读),语句为:结果如下图所示:1
select @@tx_isolation;
根据上表,当隔离级别为READ UNCOMMITTED(读未提交)时,脏读,不可重复读和幻读都可能会出现,因此,我们在实验环境里把全局的隔离级别设置为此级别,语句为:设置完后,我们需要退出当前会话(session)窗口,重新进入后可以检查下设置是否生效,如下图所示:1
set global transaction isolation level read uncommitted;
- 关闭事务自动提交
MySQL默认的事务提交模式是auto commit(自动提交),如下图所示:
为了方便实验,我们在实验环境里关闭自动提交,即改为手动提交,同样,也是全局配置,语句为:退出当前会话窗口后,重新进入检查设置是否生效,如下图所示:1
set global autocommit=0;
至此,实验所需的设置就处理好了,在环境恢复前,开启的任何MySQL命令行窗口的隔离级别都是READ UNCOMMITTED,而且事务提交模式都为手动提交。下面开始进行实验。
脏读
概念
脏读是指一个事务未提交的数据被另一个事务读到了。
实验
- 开启两个MySQL命令行窗口;
- 窗口A开启事务,并对数据进行更新,此时窗口A还未提交事务;
- 窗口B查询窗口A更新的相应数据,发现读到了窗口A还未提交的数据,如图所示:
- 窗口A进行数据回滚,窗口B再一次查询该条数据,读回来的数据仍旧是窗口A更新前的数据,如果程序里使用了回滚前的数据,则产生了脏读,如图所示:
不可重复读
概念
事务A访问了一条数据,在事务A结束前,事务B修改了该记录,此时如果事务A再一次访问该数据,会发现两次读取的结果不一样。
实验
- 开启两个MySQL命令行窗口;
- 窗口A开启事务,读取一条数据,此时窗口A还未结束事务;
- 窗口B开启事务并对窗口A读取的事务进行更新,更新完后提交;
- 窗口A再次读取该条数据,发现读取的数据与之前的不一致,出现了不可重复读,如图所示:
幻读
概念
事务A根据条件进行数据统计,在事务A结束前,事务B插入或删除了数据并提交,此时如果事务A再一次进行相同条件的数据统计,会发现两次的统计结果不一样。
实验
- 开启两个MySQL命令行窗口;
- 窗口A开启事务进行数据统计,此时窗口A还未结束事务;
- 窗口B开启事务并插入新数据,插入完后提交;
- 窗口A再次进行相同条件的数据统计,发现两次的统计结果不一致,出现了幻读,如图所示:
不可重复读与幻读的区别
不可重复读和幻读在步骤流程上看起来可能会有点类似,但其中还是有区别的。不可重复读是读取了其它事务更新的数据,针对的是update操作。幻读是读取了其它事务新增的数据,针对的是insert或delete操作
环境恢复
实验结束后,对环境恢复只需反向执行实验准备的语句即可:
1 | set global transaction isolation level repeatable read; |