谈到事务隔离级别,开发同学都能说个八九不离十。脏读、不可重复读、RC、RR...这些常见术语也大概知道是什么意思。但是做技术,严谨和细致很重要。如果对事务隔离级别的认识,仅仅停留在大概知道的程度,数据库内核研发者可能开发出令用户费解的隔离级别表现,业务研发者可能从数据库中查出与预期不符的结果。
那么如何判断自己是不是对事务隔离级别有了较为深入的理解了呢?开发同学可以问自己这样两个问题:(1)事务隔离级别分为几类?分别能解决什么问题?是否有明确定义?这样的定义是否准确?(2)当前主流数据库(Oracle/MySQL...)的隔离级别表现和实现是怎样的?是否与“官方”定义一致?
如果能清楚明白的回答这两个问题,恭喜,你对事务隔离级别认识已经非常深刻了。如果不能,也没有关系,读完本文你就有答案了。
1.事务隔离级别
事务隔离级别,主要保障关系Dirty read: SQL-transaction T1 modifies a row. SQL- transaction T2 then reads that row before T1 performs a COMMIT. If T1 then performs a ROLLBACK, T2 will have read a row that was never committed and that may thus be considered to have never existed.
Non-repeatable read: SQL-transaction T1 reads a row. SQL- transaction T2 then modifies or deletes that row and performs a COMMIT. If T1 then attempts to reread the row, it may receive the modified value or discover that the row has been deleted.
Phantom: SQL-transaction T1 reads the set of rows N that satisfy some . SQL-transaction T2 then executes SQL-statements that generate one or more rows that satisfy the used by SQL-transaction T1. If SQL-transaction T1 then repeats the initial read with the same , it obtains a different collection of rows.
嫌弃以上定义冗长,可以直接看以下形式化描述:A1 Dirty Read:w1[x] ... r2[x] ... (a1 and c2 in any order)
A2 Fuzzy Read:r1[x] ... w2[x] ... c2 ... r1[x] ... c1
A3 Phantom Read:r1[P] ... w2[y in P] ... c2 ... r1[P] ... c1
其中w1[x]表示事务1写入记录x,r1表示事务1读取记录x,c1表示事务1提交,a1表示事务1回滚,r1[P]表示事务1按照谓词P的条件读取若干条记录,w1[y in P]表示事务1写入记录y满足谓词P的条件。
据此,ANSI定义了四种隔离级别,分别解决以上三种异常:
根据上述几种异常现象定义隔离级别,可谓十分不严谨,Jim Gray大名鼎鼎的论文A Critique of ANSI SQL Isolation Levels(后文简称Critique)就对此做了批判。
不严谨之一:禁止了P1/P2/P3的事务,即满足了Serializable级别。但是在ANSI标准中又明确描述Serializable级别为“多个并发事务执行的效果与某种串行化执行的效果等价”。显然这两者是矛盾的,禁止P1/P2/P3的事务,不一定能满足“等价于某种串行执行”。所以Critique将ANSI定义的禁止了P1/P2/P3的隔离级别称为Anomaly Serializable。
不严谨之二:异常现象定义不准确,如下例并未被A1囊括,却仍然出现了Dirty Read(Txn2读到x+y!=100)。同样,A2/A3也能举出这样的例子,感兴趣的同学可以自己尝试列举,这里不再详述。
究其原因,ANSI对异象的定义太为严格,如果除去对事务提交、回滚和数据查询范围的要求,仅保留关键的并发事务之间读写操作的顺序,更为宽松且准确的异象定义如下:P1 Dirty Read: w1[x]...r2[x]...(c1 or a1)
P2 Fuzzy Read: r1[x]...w2[x]...(c1 or a1)
P3 Phantom: r1[P]...w2[y in P]...(c1 or a1)
不严谨之三:三种异象仅针对S(ingle) V(alue)系统,不足以定义M(ulti)V(ersion)系统的隔离性。很多商业数据库所实现的SI,未违反P1、P2和P3,但又可能出现Constraint violation,不可串行化。除了P1/P2/P3,还可能出现哪些异常呢?P4 Lost Update:r1[x]...w2[x]...w1[x]...c1
A5A Read Skew:r1[x]…w2[x]... w2[y]…c2…r1[y] …(c1 or a1)
A5B Write Skew:r1[x]…r2[y]…w1[y]…w2[x]…(c1 and c2 occur)
A5B2 Write Skew2:r1[P]... r2[P]…w1[y in P]…w2[x in P]...(c1 and c2 occur)
对这四种情况,分别举一个例子:r1[x=50] r2[x=50] w2[x=60] c2 w1[x=70] c1
Lost Update:事务1和事务2同时向同一个账户x分别充20和10块,事务1后提交,将70块写入数据库,事务2提交结果60块被覆盖。正确的情况下,事务1和2提交成功,账户里应该有80块。(x+y=100) r1[x=50] w2[x=10] w2[y=90] c2 r1[y=90] c1
Read Skew: x和y账户分别有50块钱,加起来共100块。事务1读x(50块)后,事务2将x账户的40块转到y账户,事务2提交后,事务1读y(90块)。在事务1看来,x+y=140,出现了不一致。(x+y>=60) r1[x=50] r2[y=50] w1[y=10] c1 w2[x=10] c2
Write Skew:x和y账户分别有50块钱,加起来共100块。假设存在某种约束,x和y账户的钱加起来不得少于60块。事务1和事务2在自认为不破坏约束的情况下(分别读了x账户和y账户),再分别从y账户和x账户取走40。但事实上,这两个事务完成后,x+y=20,约束条件被破坏。(count(P)<=4):r1[count(P)=3],r2[count(P)=3],insert1[x in P],insert2[y in P],c1,c2,
Write Skew2:将Write Skew的条件改为范围。