数据库中事务的几种隔离级别分别解决了哪些问题

一段废(huì)话(fà)

前面一直在写 JVM 系列的文章,直到有一天,卡壳了,后面不知道写啥了,缘由就是笔者是一个菜鸟(公众号名称就能看出),懂得少,理解也不够透彻,致使差很少快两个月没更了(主要仍是由于懒)。mysql

最近打开微信公众号后台一看,发现停更两月,竟然涨了 200 多个粉(上次春节也是由于懒,停更了快 3 个月,掉了 400 多个粉),内心一喜,决定仍是继续坚持下去(程序员的快乐就是这么简单),不过 JVM 系列先暂停一段时间,而是接着以前分享过的 MySQL 系列继续(不出意外,接下来几篇将主要分享 MySQL 系列和 Redis 系列的知识)。程序员

先来篇基础一点的文章,找找感受,今天讲讲事务的隔离级别,虽然很基础,可是面试时却常常被问到(初级攻城狮),因此仍是颇有必要了解一下,并且后面计划分享的两篇文章,和事务的隔离级别息息相关。web

4 种现象与 4 种隔离级别

众所周知,一般关系型数据库在高并发面前,很弱鸡,可是吧,它即便再弱鸡,在正常的使用过程当中,也存在一些并发的场景。而一出现并发,就会出现各类各样的问题,例如:脏写、脏读、不可重复读、幻读,这都是些啥呢?下面一一具体举例解释。 面试

脏写现象与读未提交(read uncommitted)隔离级别

事务 A 和事务 B 同时执行,它们都要对同一条数据进行修改(这条数据的值,假设为 data)。sql

  1. 首先是事务 A 将数据的值修改成 data1,暂时不提交事务;
  2. 而后事务 B 将数据的值修改成 data2,而后立马提交事务;
  3. 事务 A 可能因为本身的业务系统出现了异常,所以进行回滚操做,将数据的值从新回滚为 data。

在这个过程当中,事务 A 一个回滚操做,将事务 B 修改的值也回滚了,一晚上回到解放前,事务 B 白忙活一场,这种现象叫作脏写。它的本质就是一个事务将另外一个事务提交的修改操做回滚了。数据库

显然在数据库中,确定不容许这种现象存在。那么该如何解决这种问题呢?加个写锁就能解决,要对数据修改,必需要先获取到这行数据的写锁,不然不能修改。微信

在事务 A 开启时,对 data 加上写锁,直到事务 A 提交事务之后,才释放锁,在此期间,其余事务因为获取不到锁,也就谈不上对 data 数据进行修改了。并发

在实际的数据库中,则是将事务的隔离级别设置为读未提交(read uncommitted) ,在该隔离级别下,能保证事务提交以前,其余事务不能同时对这条数据进行修改。编辑器

脏读现象与读提交(read committed)隔离级别

事务 A 和事务 B 同时执行,事务 A 先将数据从 data 修改成 data1,而后暂时不提交事务,而是继续向后处理业务逻辑。高并发

而后事务 B 读取这一行数据,读取到值为 data1,而后基于 data1 这个值去处理本身的业务逻辑了。

接着事务 A 在处理后面的业务逻辑时出现了异常,所以要进行回滚操做,将数据从 data1 回滚为 data。

在这个过程当中,当事务 B 再去查询时发现数据的值为 data,这就蛋疼了,原本是基于 data1 这个值去作的业务逻辑处理,结果如今发现值倒是 data,完蛋了,全 NM 错了,这种现象就是脏读,它的本质就是一个事务读到了另外一个事务未提交的值。

为了解决脏读的问题,数据库中定义了读提交(read committed) 隔离级别,它的意思就是,在读数据的时候,只能读到别的事务提交事后的值,对于未提交的事务对数据所作的修改操做,当前事务是没法读取到的。

在读提交的事务隔离级别下,当事务 B 去读取数据时,发现事务 A 尚未提交,所以它不能读取到 data1 这个值,只能读取到 data 这个值。

不可重复读现象与可重复读(repeatable read)隔离级别

假设如今数据库中事务的隔离级别为读提交,也就是未提交的事务修改的值,其余事务是读取不到的,那么在当前事务隔离级别下还会有其余问题吗?

事务 A 和事务 B 同时开启事务,事务 A 先从数据库查询数据,读取到的值为 data,而后事务 A 先不提交事务。

接着事务 B 修改数据,将数据的值从 data 修改成 data1。

若是事务 B 先不提交事务,那么事务 A 此时来读取数据时,能读取到最新的值 data1 吗?不能,由于咱们假设了此时事务的隔离级别处于读提交状态。

好,既然不能事务 A 不能读取到最新值,那么如今事务 B 提交事务,接着让事务 A 再次从数据库查询数据,此时能读取到最新的值吗?

能,此时读取到的值为 data1,由于事务 B 已经提交了事务,在读提交的隔离级别下,提交了的事务,其余事务都能读取到最新的值。

可是这有问题啊!在同一个事务内(事务 A),它读取了两次数据,发现先后两次读取到的值分别是 data 和 data1,同一行数据,读到的值却不同,事务 A 此时内心可能 MMP 了,干啥啊,忽悠我呢!

实际上这就是不可重复读现象,它的本质就是在同一个事务内,屡次从数据库读取数据,读取到的值不同。(注意不可重复读与脏读的区别:脏读是指读到了未提交事务的值,不可重复读指的是其余事务更新数据并提交后,本身先后读取到的数据不一致

所以,可重复读(repeatable read) 事务隔离级别出现了,它的意思是,在同一个事务内,例如事务 A,屡次从数据库读取数据时,每次读取到的值是同样的,即便在此期间有其余事务修改了这条数据的值,也不会致使事务 A 先后两次读取到的值不同。

幻读现象与串行化(serializable)隔离级别

假设事务 A 和事务 B 并发执行,首先事务 A 先执行了以下 SQL,假设查到了 1 条数据;

### 假设只查询出来一条数据
select * from t where id > 1

接着事务 B 向数据库中又新插入了 10 条数据,并提交了事务;

而后事务 A 又使用一样的 SQL 语句查询数据,这时会查询出来 11 条数据,比以前查出来的数据多,也就是说看到了更多的数据,这种现象就是幻读。

注意幻读与不可重复读的区别:幻读特指在同一个事务内,先后两次查询,后面的查询,读到了以前没看到的数据;而不可重复读指的是在同一个事务内,针对同一行数据而言,先后两次查询,读取到的值不同。

为了解决幻读的问题,数据库提出了串行化(Serializable) 这种事务隔离级别。

那么什么是串行化呢?归根结底,出现脏写、脏读、不可重复读、幻读这些问题,都是由于并发致使的,那要一会儿所有解决这些问题,最简单的办法就是不要让线程并发执行,让多个线程一个一个执行,也就是串行化(也就是不让并发出现,都没有并发了,也就没有脏写、脏读、不可重复读、幻读这些幺蛾子了)。

总结

因为事务的并发执行,会形成不少异常的现象,例如脏写、脏读、不可重复读、幻读等。

这四种现象总结起来就是:

  1. 脏写指的是一个事务将其余事务提交的修改回滚了;
  2. 脏读指的是一个事务读取到了另外一个事务未提交的修改值;
  3. 不可重复读指的是一个事务对同一条数据,两次先后读取到的值不同,这是由于在此期间有其余事务更新了该条数据;
  4. 幻读指的是一个事务,后一次的查询比前一次查询看到的数据多了,它特指读到了新的数据,须要与不可重复读的现象区分开来。

为了解决这些问题,SQL 标准(注意:这里说的是 SQL 标准)中定义了 4 种事务的隔离级来应对这些现象,分别是:读未提交、读提交、可重复读、串行化,它们的强度也依次递增。在这四种隔离级别下,它们的表现以下:


脏写 脏读 不可重复读 幻读
读未提交
读提交
可重复读
串行化

实际上,这四种事务的隔离级别只是 SQL 标准中定义的,在各大数据库中,这 4 种隔离级别在实现细节上又有所不一样,例如:对于 MySQL 的 InnoDB 存储引擎而言,在可重复读隔离级别下,MySQL 经过 MVCC 机制解决了幻读的问题(在 SQL 标准中,可重复读隔离级别下仍存在幻读的问题)。

那么 MySQL 是如何经过 MVCC 机制解决幻读的,下篇文章 《MySQL 的可重复读隔离级别下还存在幻读的问题吗?MVCC 机制的实现原理》 分析(立个 flag,这篇文章这周日发)。

- END -


本文分享自微信公众号 - MySQL解决方案工程师(mysqlse)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索