原文:A Critique of ANSI SQL Isolation Levels
隔离级别定义
可序列化概述
一个事务,将一组动作聚集在一起,这组动作将数据库从一个一致的状态转换到另一个一致的状态。
在历史操作记录中,有两个事务操作同一个数据项被视为是冲突的,其中一个至少有一个是写操作。
事务之间如果有冲突关系,可以构成一个依赖图。如果两种执行拥有相同的依赖图,则说明他们是等价的。
一个执行序列是可序列化的,当它的执行序列与一个串行执行等价,即它们有相同的依赖图。等同于有一个序列,事务一次只执行序列中的一个操作一样。
ANSI SQL 隔离级别
- P1(Dirty Read,脏读): T2 读取了 T1 尚未提交的数据,之后T1执行了Rollback。
- P2(Non-repeatable 或 Fuzzy Read,不可重复读): T1 事务执行的过程中,两次读取数据项A,T2 在T1两次执行中,对A进行了修改或删除。T1的两次读取读到了不同的A的值。
- P3(Phantom,幻读): T1 在事务执行过程中多次读取了一个数据范围,T2在T1多次读取中间,对数据范围内进行了插入,导致T1多次读取的内容不同。(这里不止有insert,包括delete,update都可)
P1: w1[x]... r2[x]... ((c1 or a1) and (c2 or a2) in any order))
A1: w1[x]... r2[x]...(a1 and c2 in any order)
P2: r1[x]... w2[x]...((c1 or a1) and (c2 or a2) in any order))
A2: r1[x]... w2[x]...c2...r1[x]...c1
P3: r1[P]...w2[y in P]...((c1 or a1) and (c2 or a2) any order)
A3: r1[P]...w2[y in P]... c2... r1[P]...c1
P 表示更广泛的可能出问题的情况,A表示一定出问题的情况。P表示谓词。
上述举例都是在单版本系统中描述的。
P0(Dirty write, 脏写),T2写了T1写过的值,T1提交了实际上是T2写的值。
P0: w1[x]... w2[x]... (c1 or a1)
P4(Lost Update): 发生的情况是,T1 读取了一个数据项A,T2 对A做出修改(可能是基于T2自己之前读取的A的值),T1(基于自己之前读取到的值)更新数据项A,并提交了。
P4: r1[x]...w2[x]...w1[x]...c1
example:
H4: r1[x=100] r2[x=100] w2[x=120] c2 w1[x=130] c1
Cursor Stability is widely implemented by SQL systems to prevent lost updates for rows read via a cursor.
cursor stability:防止了 lost update
Snapshot Isolation
每个事务读取数据从一个快照中读取,这个快照是事务开始的时候获取的一个Start-Timestamp,这个Start-Timestamp是在事务开始读取诗句之前的任何时刻获取的。并且,事务运行在Sanpshot isolation下的读操作是不会阻塞的。其它事务在Start-Timestamp之后更新的数据,对于当前事务不可见。
Snapshot Isolation是多版本并发控制的一种。
当事务T1准备提交,它会再获取一个Commit-Timestamp,该Commit-Timestamp大于任何已存在的Start-Timestamp 或 Commit-Timestamp。事务能够成功提交当且仅当没有第二个事务T2持有Commit-TImestamp在T1执行的中间[Start-Timestamp, Commit-Timestamp]对数据修改,并且这些修改的数据,T1也有修改。要不然,T1会终止。这个特性叫做 先提交者胜。这样阻止了 lost-update。当T1提交,它的修改会对后续那些是它如同-TImestamps大于T1的Commit-Timestamp的事务可见。
A5:(Data Item Constraint Violation) 数据项约束违反。假设 C() 是一个数据项x和y的数据库约束。
A5A Read Skew(读偏斜):T1 读取了x,随后T2 更新了x和y并且commit了,T1读取y,此时T1发现违反了约束,因此报告了一个不一致的状态。
A5A: r1[x]... w2[x]... w2[y]... c2... r1[y]...(c1 or a1)
A5B Write Skew(写偏斜):T1 读取了 x 和 y,此时的值满足约束 C(),之后T2 读取 x 和 y,并写了x,之后提交。之后 T1 写了y。如果x和y之间有约束,此时可能已经违背了。
A5B: r1[x]... r2[y]... w1[y]... w2[x]... (c1 and c2 occur)
由此可以看出,对于Snapshot Isolation不会出现Read Skew问题,毕竟T1读取是按照快照读取的。在事务中的读取是保持一致的。不会出现读取相同的数据项获得到的值不一样的情况,即使过程中其他事务更新了该数据项,但其他事务的commit timestamp一定是大于T1的Start Timestamp的。所以,对于T1是不可见的,也就避免了read skew。
由此可以看出,对于Snapshot Isolation会出现 write skew 问题。
毕竟读取数据保证了一致,但是写入的时候两个事务分别写入的是不同的数据项,此时简单的只锁定写入行,无法避免此问题。
需要额外的检测,或者使用 for update语句,对于读取的数据行,也进行加锁。即可互斥两个事务。使其串行化。
隔离级别之间的比较
READ UNCOMMITTED << READ COMMITTED << Cursor Stability << REPEATABLE READ << SERIALIZABLE
Snapshot Isolation
其中,REPEATABLE READ 和 Snapshot Isolation之间不可比, 因为 Snapshot Isolation 能够消除幻读问题,但是存在 写偏斜 问题。但 REPEATABLE READ 能够消除write skew,但可能存在幻读问题。
Oracle Read Consistency 隔离级别给每个SQL语句最新的提交的数据,就好像每个SQL语句开始都像事务开始一样获取一个start timestamp。底层的原理是,使用语句的时间戳重新计算一个行的合适的版本。行的插入、更新和删除会被锁先锁住,使用 先写者胜,而不是先提交者胜的策略。Read Consistency 要比 READ COMMITTED要强一些。(它不允许游标丢失更新)。但是允许不可重复读、幻读、读偏斜。Snapshot Isolation 不允许丢失更新和读偏斜。
问题:
Repeatable read 存在幻读问题是因为:
Repeatable read在实现的时候,没有办法保证给整个谓词加锁,一些情况下可以通过锁定索引树的较底层节点,持有读锁,保证其他事务无法在其上加写锁。保证了读偏斜和写偏斜不会发生。但无法保证所有情况下都可以锁定范围成功。比如查询如果不走索引的情况,就无法保证不出现幻读了。
Repeatable read会对读取过的数据加锁,行锁或者间隙锁。这样会防止其它事务并发的更新。因此避免了write skew问题。