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

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

扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,阅读更多Spring源码分析Java并发编程Netty源码系列MySQL工作原理JVM专题系列文章。

微信公众号

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

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

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

先来篇基础一点的文章,找找感觉,今天讲讲事务的隔离级别,虽然很基础,但是面试时却经常被问到(初级攻城狮),所以还是很有必要了解一下,而且后面计划分享的两篇文章,和事务的隔离级别息息相关。

4 种现象与 4 种隔离级别

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

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

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

  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,这篇文章这周日发)。

微信公众号

分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改