5

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

 3 years ago
source link: https://my.oschina.net/u/4591256/blog/4896287
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
8b4cc7fa-58c0-413f-a1d3-515783fe553f.jpg

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

- END -

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK