事务隔离级别
本文内容
前言
上一篇文章中,介绍了事务的基本概念、MySQL 中的事务控制语句、事务的 ACID 特性等,本文将展开其中的 I,也就是 “隔离性”。
1. 并发事务引发的问题
当一个数据库中有 多个事务在同时执行 时,可能会出现 脏读、不可重复读、幻读 的问题。下面来看看这三个问题分别是什么现象。
1.1 脏读
所谓 脏读,意思是 一个事务(B)读取到了另一个事务(A)还没有提交的数据,那么当 A 回滚后,B 读取到的数据就成了脏数据。如下图所示:
可以发现,脏读现象是因为 读取到了别的事务未提交的数据 (可能发生回滚) 导致的。
1.2 不可重复读
不可重复读也是见名知意,即 在一个事务内,重复读取多次相同的记录,出现了记录不一样 的情况,就说明发生了不可重复读现象。如下图所示:
可以发现,不可重复读现象是因为 读取到了别的事务提交的数据 导致的。
1.3 幻读
幻读是指 在一个事务内,多次读取符合某个查询条件的记录,出现了记录数量不一样 的情况。注意,幻读强调的是读取记录的数量。如下图所示:
可以发现,幻读现象是因为 读取到了别的事务插入的数据 (已提交) 导致的。
1.4 补充:脏写
这里补充一个并发事务可能会出现的另一个现象,脏写,不过在 MySQL 中一般不会出现。
脏写 指的是一个事务(T1)先修改了数据,另一个事务(T2)也修改了同一个数据,而且还提交了。之后,T1 发生了回滚,此时也会 把 T2 已经提交的数据回滚掉。对于 T2 来说,明明已经提交了事务,但最后的数据还是原来的,这就是脏写现象。
可以发现,脏写现象是由于一个事务修改了另一个事务修改的数据导致的,但在 MySQL 中有行锁,更新操作是会加锁的,另一个事务再去更新时,会造成写写互斥,所以不会发生脏写问题。
2. 事务隔离级别有哪些?
SQL 标准的事务隔离级别包括如下:
读未提交 (Read Uncommitted):指一个事务 还没提交时,其更变就能被别的事务看见;
读提交 (Read Committed):指一个事务 提交后,其更变才会被别的事务看见;
可重复读 (Repeatable Read):指一个事务执行过程中看到的数据,总跟事务开启时看到的数据一样。RR 为 MySQL InnoDB 默认的隔离级别。
在 RR 隔离级别下,未提交的更变对其他事务也是不可见的。
串行化 (Serializable):顾名思义,对记录进行读写时都会加锁(写加写锁、读加读锁),当出现锁冲突时,后访问的事务需要等待。
从上到下,隔离级别越来越高,而 效率越来越低。
在之前介绍并发事务会引发的问题时,都说明了出现这些问题的原因,那么根据原因和以上隔离级别的保证,可以得出:不同隔离级别所解决的并发事务问题的能力是不同的:
- 要解决 脏读 问题,就要将隔离级别设置为「读提交」或更高;
- 要解决 不可重复读 问题,就要将隔离级别设置为「可重复读」或更高;
- 要解决 幻读 问题,就要将隔离级别设置为「串行化」或更高。
不同隔离级别会出现的问题如下图所示:
隔离级别 | 并发事务会出现的问题 |
---|---|
读未提交 | 脏读、不可重复读、幻读 |
读提交 | 不可重复度、幻读 |
可重复读 | 幻读 |
串行化 |
其实,MySQL 在 可重复读 隔离级别下,就可以 很大程度上避免幻读的出现,只不过不是完全避免,所以 MySQL 的默认隔离级别是可重复读,在争取 引发最少并发事务问题 的同时,以达到 最高的效率。
每种隔离级别都有各自的使用场景,需要根据自己的业务情况而定。比如业务中没有重复读取记录的事务,那么就可以将隔离级别设置为读提交。
3. 举例说明
下面例举一个具体的例子,来看看在不同的隔离级别下,并发事务引发的问题。
假设表 T 中有一个字段,只有一条记录,值为 1,两个事务并发的执行:
来看看在不同的隔离级别下,事务 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. 参考文章
- 《MySQL 实战 45 讲》
- 小林 coding