MySQL的四种事务隔离级别

1.事务的特性(ACID原则)

  • 原子性(Automicity): 一个事务作为不可分割的最小单元,一个事务里面的所有操作要么全部成功,要么全部失败(即:将多个操作数据库的动作捆绑在一起。如将多个修改表数据的操作捆绑在一起,只有当所有的修改操作都执行成功后,这个事务才视为成功;否则其中一个修改操作出现错误时,整个事务都将视为失败,并且在错误出现前执行成功的所有操作都需要回滚为此前未修改时的状态)

  • 一致性(Consistency): 事物结束后系统状态是一致的,数据不能平白无故的产生,也不能平白无故的消失(如:微信好友间的转账服务,你的朋友向你转账100元,此时你的账户余额少了100,你的朋友多了100,但你们最终的余额总和是与转账前保持一致的~!)

  • 隔离性(Isolation):一个事务所有的操作,在最后Commit(提交)之前,所有修改对其他事务不可见(使用过Git的读者朋友们都知道,在与别人一起合作写代码时,双方都需要在远程的代码仓库拉取中代码保存在自己的本地仓库,并且在各自的本地仓库对代码进行编写或修改,最后再将本地仓库写好的代码上传至远程仓库中。在这个过程里,本地仓库代码在没有上传到远程仓库前,双方都是无法得知对方本地仓库代码做了什么变动的,二者的本地仓库都是相对隔离开来的)

  • 持久性(Durability): 当事务提交后,数据应该永久被保存到数据库中,即使发生了灾难性后果,数据也不会丢失(简单说就是,将数据存入到磁盘中~!)


2.事务可能产生的问题

事务可能发生的问题大概分为以下三种:

  1. 脏读: 脏读是读到了别的事务回滚前的脏数据。比如事务B执行过程中修改了数据X,在未提交前,事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。
  2. 幻读: 当事务A第一次读取某个范围记录后,事务B新增了一条记录,事务A在此读取这个范围是会多一条记录导致幻读出现。
  3. 不可重复读: 事务A查询了一条数据后,事务B对该条数据进行修改,此时当事务A再次查询该数据时,得到的结果与第一次查询的不一致。

接下来我们用几张图来分别说明以上三个问题~!


2.1.脏读

MySQL的四种事务隔离级别_第1张图片
在上图中: 事务A、B同时开启了事务,首先A执行了一次查询操作,接着B对此条数据进行修改操作,当A再次执行相同的查询操作时读取到了B修改后的数据,但此时B因为某些原因需要将整个事务进行回滚后,A再一次执行查询操作,数据恢复到与第一次查询相同的结果。

脏读会导致什么问题: 这个可以结合我们熟悉的商品限量秒杀系统来分析。首先秒杀系统通过事务A获取到商品库存数量,第一次查询结果是18,此时我们认为商品剩余库存就是18,当18件商品全部卖出,后面请求购买的用户都会被程序拒绝。但如果A读取到了B修改的数据,此时程序认为商品剩余库存是20,但是实际上B事务是需要回滚的,即最终商品的库存还是只有18,库存实际上并未改变,那么此时当18件商品卖出去后,购买第1920件商品的用户请求将不会被程序拒绝,因为程序此时认为商品库存是20,卖出18件商品后还有两件商品未卖出,继续处理后来用户的购买请求,因此出现了超卖两件商品的现象,而同时这种情况在我们程序猿的眼中是绝对不允许存在的问题~!!


2.2.幻读

MySQL的四种事务隔离级别_第2张图片
在上图中: 事务A、B同时开启事务,首先事务A查询id>0的所有数据,结果为一条数据,此时事务B向表中插入两条id>0的新数据并提交事务,之后事务A再次执行与之前相同的查询语句,查询结果变为了三条数据。前后两次查询到的数据条数不相同,这就是幻读(其中:第二次查询结果与第一次查询多出来的行数我们称为幻行)。


2.3.不可重复读

MySQL的四种事务隔离级别_第3张图片
在上图中: 事务A、B同时开启事务,首先事务A对id=1的商品数据进行查询,此时事务B对此条数据进行修改并提交事务,之后事务A再次执行相同的查询语句时,获取到的是修改后的数据,与第一次查询的并不一致,也就是所谓的不可重复读。


3.事务的四种隔离级别

为了解决脏读、幻读、不可重复读等问题,MySQL提供了四种隔离级别供我们选择,分别如下:

  1. 未提交读:READ UNCOMMITTED 在此级别下脏读、幻读、不可重复读等问题都会出现。
  2. 已提交读:READ COMMITTED 此级别下解决了脏读的问题,存在幻读和不可重复读(同时,这也是大多数数据库默认的事务隔离机制)。
  3. 可重复读:REPEATABLE READ 此级别下解决了脏读和不可重复读的问题,但仍然存在幻读的问题(同时,这是MySQL默认引擎InnoDB引擎的默认事务隔离级别)。
  4. 可串行化:SERIALIZABLE 此级别下脏读、幻读已经不可重复读等问题都将不复存在,在次级别下每条操作命令都必须等到上一条命令执行完毕后才可以开始执行,此时我们的数据库可以看成是一个单线程的程序,数据是绝对安全的,但却严重影响程序的运行效率,只有但数据安全性的要求大大高于运行效率要求时我们才使用该隔离级别(如:银行转账系统)。

四种级别处理的问题概况表如下:

隔离级别 脏读 不可重复读 幻读
未提交读:READ UNCOMMITTED 存在 存在 存在
已提交读:READ COMMITTED 不存在 存在 存在
可重复读:REPEATABLE READ 不存在 不存在 存在
可串行化:SERIALIZABLE 不存在 不存在 不存在

值得注意的是: MySQL默认引擎InnoDB引擎的默认隔离级别(可重复读),采用了MVCC(多事务版本控制)解决了幻读的问题。


4.测试MySQL的各种隔离级别

接下来我们将通过具体的操作来测试一下MySQL的各种隔离级别,一下是设置MySQL各种隔离级别的具体命令:

# 未提交读级别
set session transaction isolation level read uncommitted;

# 已提交读级别
set session transaction isolation level read committed;

# 可重复读级别
set session transaction isolation level repeatable read;

# 可串行化级别
set session transaction isolation level serializable;

4.1.未提交读

# 设置事务隔离机制为未提交读级别
set session transaction isolation level read uncommitted;

MySQL的四种事务隔离级别_第4张图片
上图命令执行顺序如下:

  1. 事务A、B:begin;(开启事务 )
  2. 事务A:select * from user where id = 1(查询user表中id=1的数据,等待结果name=“智一”)
  3. 事务B:update user set name = "伊治" where id = 1(将id=1的数据name从“智一”改为“伊治”)
  4. 事务A:select * from user where id = 1(再次查询id=1的数据,此时事务A读取到了事务B未提交的数据,也就是我们称之为的脏读)
  5. 事务B:rollback(回滚事务)
  6. 事务A:select * from user where id = 1(第三次查询id=1的数据,此时name恢复为了第一次查询时的“智一”)

经过上述操作,我们知道了在未提交读(read uncommitted)级别下,会存在脏读的问题。(还会存在不可重复读、幻读等问题,这部分因为篇幅问题笔者不再进行演示,感兴趣的读者可以自己动手尝试)。


4.2.已提交读

# 设置事务隔离机制为已提交读级别
set session transaction isolation level read committed;

MySQL的四种事务隔离级别_第5张图片
上图命令的执行与测试未提交读时类似,区别只在于回滚事务rollback的操作换为了提交事务commit的操作~!

经过上述操作,我们知道了在已提交读(read committed)级别下,事务A无法读取事务B未提交的数据,即脏读现象。但在事务A中,同一个查询语句查询出来的结果前后不一致(第一次查询和第三次查询),也就是不可重复读问题。


4.3.可重复读

在前面,我们说过,MySQL默认引擎InnoDB引擎的默认隔离级别(可重复读),采用了MVCC(多事务版本控制)解决了幻读的问题,因此我们将来测试MySQL在RR级别下是否同时解决了可重复读和幻读的问题。

# 设置事务隔离机制为可重复读级别
set session transaction isolation level repeatable read;

首先测试可重复读:
MySQL的四种事务隔离级别_第6张图片
从上图中,我们发现:即使事务B提交了修改操作,事务A对此条数据的查询还是保持与第一次查询时一致,即可重复读

然后是幻读:
MySQL的四种事务隔离级别_第7张图片
从上图我们可以得知:即使事务B对数据库进行了插入新数据的操作,事务A的查询结果仍然保持与第一次的查询一致,并不会出现第二次查询的结果集比第一次的结果集多出一条数据的情况,即不会出现幻行。

对于MySQL的事务隔离级别就讲到这里啦,若文章存在错误还望各位读者指出~!!

MySQL的四种事务隔离级别_第8张图片

你可能感兴趣的