SQL 标准与 MySQL 的隔离级别
复习
(重要的事情说三遍~)
事务的 ACID 特性<span class=“hint–top hint–rounded” aria-label="王珊,萨师煜:数据库系统概论(第五版)。高等教育出版社
">[1]
-
Atomicity
原子性:事务是数据库的逻辑工作单位,事务中包含的诸操作要么都做,要么都不做
-
Consistency
一致性:事务执行的结果是使数据库从一个一致性状态变为另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致状态。可见事务的一致性与原子性密不可分。
-
Isolation
隔离性:一个事务的执行不能被其他事务干扰。
-
Durability
持久性:一个事务一旦提交,那么它对数据库中数据的改变就应该是永久性的。
为什么需要隔离?
我们知道,「事务」的存在,其实是为事务中的诸操作提供了「一致性」的保证:事务中的所有操作,要么都做,要么都不做。于是,当事务之间是串行执行时,在一个事务开始执行时,它面对的是上一个事务执行结束时的某个一致性状态,自身执行结束后,又转移到了另一个一致的状态。
但是,出于效率原因,数据库管理系统不可能对所有待执行的事务采取串行调度策略,必然是让多个事务并发执行的,即一个事务没有完全执行完,就调度了另一个事务。当并发执行的事务存在对同一数据的访问时(读-写,写-读,写-写。注意到读-读并不会带来一致性问题),事情就变得麻烦起来了。
我们观察下面几种调度情况与它们带来的一致性问题[2]:
脏写
一个事务 T2 修改了另一个事务 T1 尚未提交或回滚的修改过的数据。如果 T1 或者 T2 随后做了回滚,那么将不知道哪一版的数据是正确的。
脏写比以下提到的三种情况都要严重,它会破坏数据库的一致性、原子性和持久性:
-
场景一
设我们有一个一致性约束,即 x 和 y 的值始终相等,考虑 T1 和 T2 事务如下的执行序列:
W1(x=1) W2(x=2) W2(y=2) COMMIT2 W1(y=1) COMMIT1
如上面的事务调度序列,T2 事务写了 T1 尚未提交的已经写了的数据,虽然这两个事务各自的操作都保证了 x 等于 y 的一致性约束,但由于脏写的存在,这种一致性约束被破坏了。
-
场景二
设 x 和 y 初始值为 0,考虑 T1 和 T2 事务如下的执行序列:
W1(x=2) W2(x=3) W1(y=3) COMMIT2 ROLLBACK1
如上面的事务调度序列,T2 事务写了 T1 尚未提交的已经写了的数据,出现了脏写现象。现在考虑最后对于 T1 的回滚。如果将 T1 回滚,那么此时 x 将被修改为最开始的 0,y 被修改为最开始的 0。可是 T2 事务已经修改了 x 并成功提交,如果此时修改 x,就破坏了 T2 的持久性。可是如果不将 x 改为 0,仅将 y 改为 0,就破坏了 T1 的原子性。于是脏写使得我们面临了两难境地。
正是由于脏写很严重,下文提到的不同隔离级别,虽然对脏读、幻读、不可重复读的容忍情况各不相同,但是均不允许脏写的发生。
脏读
Dirty Read.
广义解释:一个事务读到了另一个未提交事务修改过的数据。脏读也会引起不一致问题,设我们有一致性约束:x 和 y 始终相等。考虑 T1 和 T2 如下的执行序列,设最初的 x 和 y 都是 0。
W1(x=1) R2(x=1) R2(y=0) COMMIT2 W1(y=1)
可以看到 T2 读到了 T1 已经修改过的未提交的数据,使得读出的 x 和 y 处于不相等的状态。但是最终的数据库是处于一直状态的(x 和 y 相等)
严格解释:一个事务 T2 读到了另一个事务 T1 尚未提交或回滚的修改过的数据。如果 T1 回滚了,那么 T2 就读到一个从未存在过的数据。
可见广义解释是包含了严格解释的。
幻读
Phantom.
一个事务 T1 按某种条件 C 读到了一些记录。一个事务 T2 又向数据库中 INSERT 了满足条件 C 的记录并提交。T1 继续按条件 C 读取记录,发现这次读到一些上次读不存在的记录。这些之前不存在的记录也被称为「幻影记录」。
不可重复读(模糊读)
Non-repeatable Read or Fuzzy Read.
如果一个事务 T2 修改了另一个未提交事务 T1 读取的数据,那么就发生了不可重复读。
严格解释:T1 先读取了 x 的值,然后 T2 修改了 x 的值并提交,导致 T1 再次读取 x 时读出的值不一样
通过观察上面几种一致性问题,我们发现,它们均是由于不正确的调度顺序导致的。或者换言之,就是在一个事务尚未结束对一个数据的访问时(这里的「结束」可以是提交,也可以是回滚),另一个事务也「插队」进来,对这些数据进行访问。
所谓「隔离」,就是通过某种规则将事务的诸操作进行「排队」执行,以消除脏读、幻读、不可重复读。
ANSI SQL 标准的隔离级别
之前说到,如果所有事务都是串行执行的,那么将不会出现上面提到的任何一致性问题。但问题就是,串行执行会严重降低系统的性能。如若我们只考虑并发性能,即「见缝插针」地执行不同事务,又会带来严重的一致性问题。于是,为了在保证并发性能与保证一致性之间做出平衡,ANSI SQL 标准提出了 4 种隔离级别。总而言之,就是「舍弃一部分隔离性,承担一致性问题的风险,来换取并发性能」。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 可能 | 可能 | 可能 |
READ COMMITED | 不可能 | 可能 | 可能 |
REPEATABLE READ | 不可能 | 不可能 | 可能 |
SERIALIZABLE | 不可能 | 不可能 | 不可能 |
MySQL 实现的隔离级别
- REPEATABLE READ(默认)
- READ COMMITED
- READ UNCOMMITTED
- SERIALIZABLE
注意 MySQL 的 REPEATABLE READ 级别已经可以在很大程度上防止幻读的发生
隔离的实现
todo
Ref
- 王珊,萨师煜:数据库系统概论(第五版)。高等教育出版社 ↩
- Hal Berenson, Phil Bernstein, Jim Gray, Jim Melton, Elizabeth O’Neil, and Patrick O’Neil. 1995. A critique of ANSI SQL isolation levels. SIGMOD Rec. 24, 2 (May 1995), 1–10. https://doi.org/10.1145/568271.223785 ↩