跳至主要內容

事务隔离级别

AruNi_Lu数据库MySQL约 1848 字大约 6 分钟

本文内容

前言

上一篇文章中,介绍了事务的基本概念、MySQL 中的事务控制语句、事务的 ACID 特性等,本文将展开其中的 I,也就是 “隔离性”。

1. 并发事务引发的问题

当一个数据库中有 多个事务在同时执行 时,可能会出现 脏读、不可重复读、幻读 的问题。下面来看看这三个问题分别是什么现象。

1.1 脏读

所谓 脏读,意思是 一个事务(B)读取到了另一个事务(A)还没有提交的数据,那么当 A 回滚后,B 读取到的数据就成了脏数据。如下图所示:

image-20231206204221577

可以发现,脏读现象是因为 读取到了别的事务未提交的数据 (可能发生回滚) 导致的。

1.2 不可重复读

不可重复读也是见名知意,即 在一个事务内,重复读取多次相同的记录,出现了记录不一样 的情况,就说明发生了不可重复读现象。如下图所示:

image-20231206204639105

可以发现,不可重复读现象是因为 读取到了别的事务提交的数据 导致的。

1.3 幻读

幻读是指 在一个事务内,多次读取符合某个查询条件的记录,出现了记录数量不一样 的情况。注意,幻读强调的是读取记录的数量。如下图所示:

image-20231206205611977

可以发现,幻读现象是因为 读取到了别的事务插入的数据 (已提交) 导致的。

1.4 补充:脏写

这里补充一个并发事务可能会出现的另一个现象,脏写,不过在 MySQL 中一般不会出现。

脏写 指的是一个事务(T1)先修改了数据,另一个事务(T2)也修改了同一个数据,而且还提交了。之后,T1 发生了回滚,此时也会 把 T2 已经提交的数据回滚掉。对于 T2 来说,明明已经提交了事务,但最后的数据还是原来的,这就是脏写现象。

image-20231206212114666

可以发现,脏写现象是由于一个事务修改了另一个事务修改的数据导致的,但在 MySQL 中有行锁,更新操作是会加锁的,另一个事务再去更新时,会造成写写互斥,所以不会发生脏写问题。

2. 事务隔离级别有哪些?

SQL 标准的事务隔离级别包括如下:

  • 读未提交 (Read Uncommitted):指一个事务 还没提交时,其更变就能被别的事务看见;

  • 读提交 (Read Committed):指一个事务 提交后,其更变才会被别的事务看见;

  • 可重复读 (Repeatable Read):指一个事务执行过程中看到的数据,总跟事务开启时看到的数据一样。RR 为 MySQL InnoDB 默认的隔离级别。

    在 RR 隔离级别下,未提交的更变对其他事务也是不可见的。

  • 串行化 (Serializable):顾名思义,对记录进行读写时都会加锁(写加写锁、读加读锁),当出现锁冲突时,后访问的事务需要等待。

从上到下,隔离级别越来越高,而 效率越来越低

在之前介绍并发事务会引发的问题时,都说明了出现这些问题的原因,那么根据原因和以上隔离级别的保证,可以得出:不同隔离级别所解决的并发事务问题的能力是不同的

  • 要解决 脏读 问题,就要将隔离级别设置为「读提交」或更高;
  • 要解决 不可重复读 问题,就要将隔离级别设置为「可重复读」或更高;
  • 要解决 幻读 问题,就要将隔离级别设置为「串行化」或更高。

不同隔离级别会出现的问题如下图所示:

隔离级别并发事务会出现的问题
读未提交脏读、不可重复读、幻读
读提交不可重复度、幻读
可重复读幻读
串行化

其实,MySQL 在 可重复读 隔离级别下,就可以 很大程度上避免幻读的出现,只不过不是完全避免,所以 MySQL 的默认隔离级别是可重复读,在争取 引发最少并发事务问题 的同时,以达到 最高的效率

每种隔离级别都有各自的使用场景,需要根据自己的业务情况而定。比如业务中没有重复读取记录的事务,那么就可以将隔离级别设置为读提交。

3. 举例说明

下面例举一个具体的例子,来看看在不同的隔离级别下,并发事务引发的问题。

假设表 T 中有一个字段,只有一条记录,值为 1,两个事务并发的执行:

image-20231206225305085

来看看在不同的隔离级别下,事务 A 会有哪些不同的结果:

  • 读未提交:
    • V1 = 2:虽然事务 B 还没提交,但依旧能读取到事务 B 执行的修改;
    • V2 = 2:事务 B 提交后,自然也能读取到最新值;
    • V3 = 2:理由同上。
  • 读提交:
    • V1 = 1:事务 B 还没提交,所以只能读取到之前的值;
    • V2 = 2:事务 B 已经提交了,能读取到最新值;
    • V3 = 2:理由同上。
  • 可重复读:
    • V1 = 1:事务 B 还没提交,所以只能读取到之前的值;
    • V2 = 1:事务 B 虽然已经提交了,但在事务期间读取到的值和事务开启时的一样
    • V3 = 2:事务 A 已经结束,自然读取到了最新值。
  • 串行化:
    • V1 = 1:事务 A 开启后查询到值 1,此查询会加读锁,所以事务 B 的修改操作会阻塞(读写冲突),故 V1 还是 1;
    • V2 = 1:理由同上;
    • V3 = 2:事务 A 结束,释放读锁,此时事务 B 才继续执行,提交后,V3 能读取到最新值 2。(注意按照时间线顺序,查询 V3 在提交事务 B 后面执行)

是不是觉得很神奇呢?这四个隔离级别是如何保证的呢?下一篇文章将详细介绍隔离级别的实现机制。

4. 参考文章

上次编辑于: