事务隔离是数据库系统设计的基础部分。本文主要从标准级别来探讨隔离级别的发展历史。一是明确了隔离级别划分的目标;开发过程;然后引出Adya给出的更合理的隔离级别定义,最后总结一下一路走来的隔离标准思路。目标事务隔离是事务并发的直接要求。最直观的隔离和保证正确性的方式显然是让并发事务顺序执行,或者看起来是顺序执行。但在实际场景中,有时并不需要如此高的正确性保证,因此希望牺牲一些正确性来提高整体性能。通过区分不同强度的隔离级别,用户可以在正确性和性能之间自由权衡。随着数据库产品数量和使用场景的扩大,带来了各种隔离级别选择上的困惑。许多数据库的设计者和用户迫切需要对隔离级别的划分达成共识。这就是标准出现的意义所在。一个好的隔离级别定义有以下两个重要目标:正确性:每个级别的定义应该能够排除所有损害该级别想要保证的正确性的情况。也就是说,只要实现满足某个隔离级别的定义,就可以获得相应的正确性保证。实现无关:并发控制的常见实现包括锁、OCC、多版本。一个好的标准不应该限制它的实施方式。ANSISQL标准(1992):基于1992年的异常,ANSI首先尝试规定了统一的隔离级别标准,定义了不同级别的异常(现象),并根据可以避免多少异常来划分隔离标准。异常包括:脏读:读到的数据还没有被其他事务提交;Non-Repeatable/FuzzyRead:由于其他事务的修改或删除,对某个数据的两次读取结果不同;幻读(PhantomRead):由于其他事务的修改、增加或删除,导致Range的结果无效(如where条件查询)。通过防止不同的异常发生,得到四种不同级别的隔离标准:ANSISQL标准似乎是一种非常直观的划分方式,排除你不想要的,实现与实现无关。然而,现实并没有想象的那么美好。因为这不是真的。ACritiqueofANSI(1995):基于锁几年后,微软研究人员在文章ACritiqueofANSISQLIsolationLevels中批评了ANSI标准,指出存在两个致命的问题:1、不完整、缺乏排除DirtyWriteANSISQL标准中所有的隔离级别都不排除DirtyWrite的异常。所谓脏写,是指两个未提交的事务先后修改同一个对象。脏写之所以是异常,主要是因为它会导致以下一致性问题:constraintx=y,T1尝试修改为1,T2尝试修改为2,顺序执行的结果应该是都是1或者都是2,但是由于出现DirtyWrite,最后结果变成了x=2,y=1,不一致。2.歧义ANSISQL的英文表达是有歧义的。以Phantom为例,下图中的historyH3:H3:r1[P]w2[insertytoP]r2[z]w2[z]c2r1[z]c1假设T1根据条件P,然后T2增加了一个员工,加上员工人数的值z。之后,T1读取员工数量z。最终T1的名单人数小于z,不一致。但是T1在T2修改链表后并没有使用P中的值。不属于ANSI中Phantom的定义吗?这也导致了对ANSI的严格和宽松解释的可能性。ReadDirty和Non-Repeatable/FuzzyRead也有同样的问题。那么,如何解决以上两个问题呢?CritiqueofANSI的回答是:错杀三千,不如放过一个,即在ANSI标准中给出了最严格的视觉定义。CritiqueofANSI改革了视觉的定义:P0:w1[x]…w2[x]…(c1ora1)(DirtyWrite)P1:w1[x]…r2[x]…(c1ora1)(DirtyWrite)Read)P2:r1[x]…w2[x]…(c1ora1)(FuzzyorNon-RepeatableRead)P3:r1[P]…w2[yinP]…(c1ora1)(Phantom)时间定义已经很严格了,直接阻止了相应的读写组合顺序。仔细可以看出,此时得到的其实是根据锁的定义:ReadUncommitted,preventP0:在整个事务阶段加长对x的写锁ReadCommitted,preventP0,P1:shortreadlock+长写锁RepeatableRead,防止P0,P1,P2:长读锁+短谓词锁+长写锁Serializable,防止P0,P1,P2,P3:长读锁+长谓词锁+长写锁的本质问题可以看出,这个方法的隔离定义保证了正确性,但是它产生了一个依赖于实现的问题:过于严格的隔离定义阻止了在Optimize或者Multi-version的实现中一些正常的情况:forP0:theimplementationOptimize可能会让多个事务写入自己的本地副本。提交的时候只要顺序合适就可以成功,需要的时候才abort,但是这个选择被P0挡住了;对于P2:只要T1没有读x,就没有跟x相关的操作,在T2之前提交。在优化实施中可接受,但被P2阻止。回想一下CritiqueofANSI中指出的ANSI标准问题,包括DirtyWrite和ambiguity,其实都是由多个Object之间的相互约束关系造成的,如下图所示。像描述的异常情况,灰色部分是多Object约束导致的异常部分,但是这部分不能用传统的异常定义方式描述,所以只能退而求其次,把limit的范围扩大到黄色部分,以便正常情况受到限制。由此可以看出问题的本质:由于异常的描述只是针对单个对象,缺少对多个对象之间的约束关系的描述,导致需要使用锁来做更多的限制必要的。相应地,解决问题的关键就是要有一个新的定义异常的模型,让它能够准确地描述多个对象之间的约束关系,这样我们就可以精确地限制上面提到的灰色部分,解放黄色部分。Adya给出的答案是序列化图。AGeneralizedTheory(1999):基于序列化图Adya在WeakConsistency:AGeneralizedTheoryandOptimisticImplementationsforDistributedTransactions中给出了基于序列化图的定义。思路是先定义冲突关系;并且冲突关系由有向边组成序列化图;然后不同的异常由图中的环类型定义;最后,隔离级别是通过防止不同的异常来定义的。序列化图(DirectSerializationGraph,DSG)是一种有向图,用来表示交易之间的依赖关系。图中的每个节点代表一个事务,有向边代表一个依赖关系。需要等到所有指向它的交易都先提交,如下图,合法的历史提交顺序应该是:T1,T2,T3:这里的有向边包括三种情况:Write-writeconflictww(DirectlyWrite-Depends):表示两个事务先后修改同一个数据库对象(w1[x]...w2[x]...);write-first-readconflictwr(DirectlyRead-Depends):一个事务修改了一个数据库Object后,另一个Object执行了读操作(w1[x]...r2[x]...);Read-before-writeconflictrw(DirectlyAnti-Depends):一个事务读取一个Object或某个Range后,另一个事务进行Modified(r1[x]…w2[x]…orr1[P]…w2[yinP]);基于序列化图的异常定义:根据有向图的定义,我们可以将事务分配给不同的对象依赖关系在同一个图中表示,所谓异常就是在图中找不到正确的序列化顺序,即是,有某种循环。而这个ring-based的定义其实就是基于Lock定义的异常最小化到图的灰色部分:1.P0(DirtyWrite)最小化到G0(WriteCycles):序列化图包含两条边是Aww冲突组成的环,如H0:H0:w1[x]w2[x]w2[y]c2w1[y]c1可以看出T1在x上和T2冲突,T2在y上写和T1写冲突形成一个环,如下图所示。2.P1(DirtyRead)最小化为G1:DirtyRead异常的最小集合包括三部分G1a(AbortedReads),读到的未提交数据最终被abort;G1b(IntermediateReads):读取其他事务的中间版本G1c(CircularInformationFlow):DSG包含ww冲突和wr冲突形成的环。3、P2(FuzzyorNon-RepeatableRead)最小化为G2-item(ItemAnti-dependencyCycles):DSG包含一个循环,并且至少有一个关于某个对象的rw冲突4、P3(Phantom)最小化为G2(Anti-dependencyCycles):DSG包含cycles,其中至少有一个是rwconflict,仍以上述H3为例:H3:r1[P]w2[insertytoP]r2[z]w2[z]c2r1[z]c1T1在谓词P上与T2rw冲突,进而T2在z上与T1wr冲突,如下图所示:对应的隔离级别:从上面的讨论可以看出,通过ring我们已经成功地最小化了异常的范围,然后排除这些异常导致了一个更宽松的、通用的隔离级别定义:PL-1(未提交读):防止G0PL-2(已提交读):防止G1PL-2.99(可重复Read):BlockG1,G2-itemPL-3(Serializable):BlockG1,G2对于其他隔离级别,商业数据库实现中常见的有两种:1.CursorStability这个隔离级别介于ReadCommitted和RepeatableRead之间,通过锁定游标而不是在对象上加读锁来避免。丢失写入异常。2.SnapshotIoslation在事务开始时对Start-Timestamp进行快照。所有操作都在这个快照上执行。提交时,取Commit-Timestamp并检查所有冲突的值。Timestamp]已提交,否则中止。长期以来,SnapshotIoslation一直被认为是Serializable,但实际上SnapshotIoslation下会出现WriteSkew的现象。后续文章会详细介绍如何从SnapshotIoslation中获取Serializable。综上所述,数据库前人对事务隔离级别的标准进行了长期探索:ANSI隔离级别定义了异常标准,根据排除的异常定义了四种定义:ReadUncommitted、ReadCommitted、可重复读取和可序列化隔离级别;ACritiqueofANSISQLIsolationLevels认为,ANSI的定义并不排除多对象约束的异常,而是选择使用更严格的Lock-based定义来扩大每个级别的限制范围;WeakConsistency:AGeneralizedTheoryandOptimisticImplementationsforDistributedTransaction认为基于Lock的定义将限制范围扩大太多,导致正常情况被排除,从而限制了Optimize类型并行控制的使用;指出解决这个问题的关键是要有一个模型能够准确描述这个多对象约束;并给出了一种基于序列化图的定义方法,使每一级限制的范围最小化。请参阅事务历史ANSI隔离级别的历史对ANSISQL隔离级别的批评弱一致性:分布式事务的广义理论和乐观实现广义隔离级别定义
