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

实验环境及准备

环境

  1. MySQL版本:5.7.28
  2. 数据库:sakila,这个是MySQL官网提供的数据库样例
  3. 数据表:actor

准备

在接下来的实验里,我们将开启两个MySQL命令行窗口,所以我们需要先做好一些全局设置,主要包括隔离级别的设置和事务提交模式的设置。

  1. 设置隔离级别
    MySQL默认的隔离级别为REPEATABLE READ(可重复读),语句为:
    1
    select @@tx_isolation;
    结果如下图所示:
    img
    根据上表,当隔离级别为READ UNCOMMITTED(读未提交)时,脏读,不可重复读和幻读都可能会出现,因此,我们在实验环境里把全局的隔离级别设置为此级别,语句为:
    1
    set global transaction isolation level read uncommitted;
    设置完后,我们需要退出当前会话(session)窗口,重新进入后可以检查下设置是否生效,如下图所示:
    img
  2. 关闭事务自动提交
    MySQL默认的事务提交模式是auto commit(自动提交),如下图所示:
    img
    为了方便实验,我们在实验环境里关闭自动提交,即改为手动提交,同样,也是全局配置,语句为:
    1
    set global autocommit=0;
    退出当前会话窗口后,重新进入检查设置是否生效,如下图所示:
    img

至此,实验所需的设置就处理好了,在环境恢复前,开启的任何MySQL命令行窗口的隔离级别都是READ UNCOMMITTED,而且事务提交模式都为手动提交。下面开始进行实验。

脏读

概念

脏读是指一个事务未提交的数据被另一个事务读到了。

实验

  1. 开启两个MySQL命令行窗口;
  2. 窗口A开启事务,并对数据进行更新,此时窗口A还未提交事务;
  3. 窗口B查询窗口A更新的相应数据,发现读到了窗口A还未提交的数据,如图所示:
    img
  4. 窗口A进行数据回滚,窗口B再一次查询该条数据,读回来的数据仍旧是窗口A更新前的数据,如果程序里使用了回滚前的数据,则产生了脏读,如图所示:
    img

不可重复读

概念

事务A访问了一条数据,在事务A结束前,事务B修改了该记录,此时如果事务A再一次访问该数据,会发现两次读取的结果不一样。

实验

  1. 开启两个MySQL命令行窗口;
  2. 窗口A开启事务,读取一条数据,此时窗口A还未结束事务;
  3. 窗口B开启事务并对窗口A读取的事务进行更新,更新完后提交;
  4. 窗口A再次读取该条数据,发现读取的数据与之前的不一致,出现了不可重复读,如图所示:
    img

幻读

概念

事务A根据条件进行数据统计,在事务A结束前,事务B插入或删除了数据并提交,此时如果事务A再一次进行相同条件的数据统计,会发现两次的统计结果不一样。

实验

  1. 开启两个MySQL命令行窗口;
  2. 窗口A开启事务进行数据统计,此时窗口A还未结束事务;
  3. 窗口B开启事务并插入新数据,插入完后提交;
  4. 窗口A再次进行相同条件的数据统计,发现两次的统计结果不一致,出现了幻读,如图所示:
    img

不可重复读与幻读的区别

不可重复读和幻读在步骤流程上看起来可能会有点类似,但其中还是有区别的。不可重复读是读取了其它事务更新的数据,针对的是update操作。幻读是读取了其它事务新增的数据,针对的是insert或delete操作

环境恢复

实验结束后,对环境恢复只需反向执行实验准备的语句即可:

1
2
set global transaction isolation level repeatable read;
set global autocommit=1;

参考

  1. MySQL官方数据库数据样例