原英文为《A_Critique_of_ANSI_SQL_Isolation_Levels》论文,这里是sunbowen对该论文的全文翻译。
对ANSI SQL隔离级别的评论 写作时间1995-06
ANSI:美国国家标准学会(American National Standards Institute: ANSI)成立于1918年
摘要:ANSI SQL-92[MS,ANSI]根据现象定义隔离级别:脏读、不可重复读和幻影。
本文表明,这些现象和ansi sql定义无法描述几个流行的隔离级别,包括这些级别的标准锁定实现。
对现象的模糊性进行调查可以得出更清晰的定义;此外,还引入了新的现象,更好地描述了隔离类型。定义了一种重要的多版本隔离类型,快照隔离。
目录:
1.介绍
2.隔离定义
2.1 可串行化概念
2.2 ANSI SQL隔离级别
2.3 Locking 锁定
\3. 分析ANSI SQL隔离级别
\4. 其他隔离类型
4.1 游标稳定性
4.2 快照隔离
4.3 Other Multi-Version Systems 其他多版本系统
\5. 摘要和结论
**1.**介绍
在不同的隔离级别上运行并发事务允许应用程序设计者以吞吐量换取正确性。较低的隔离级别会增加事务并发性,但有可能使事务显示为模糊或不正确的数据库。令人惊讶的是,某些事务可以在最高隔离级别(完美序列化)下执行,而在较低隔离级别下运行的并发事务可以访问尚未提交的状态或事务读取较早的过期状态[GLPT]。当然,在较低隔离级别上运行的事务可能会产生无效数据。应用程序设计人员必须防止以后以更高隔离级别运行的事务访问此无效数据并传播错误。ANSI/ISO SQL-92规范[MS,ANSI]定义了四个隔离级别:(1)读取未提交,(2)读取已提交,(3)可重复读取,(4)可序列化。这些级别是用经典的可序列化性定义定义的,加上三个被禁止的操作子序列,称为现象:脏读、不可重复读和幻影。ANSI规范中没有明确定义现象的概念,但规范表明现象是可能导致异常(可能是不可序列化)行为的动作子序列。
当我们建议增加ANSI现象集时,我们参考了以下内容中的异常情况。如下文所示,异常和现象之间存在技术上的区别,但这种区别对于一般理解并不重要。ANSI隔离级别与锁调度程序的行为有关。一些锁调度器允许事务改变其锁请求的范围和持续时间,从而脱离了纯两阶段锁定。这个想法是由[GLPT]引入的,它通过三种方式定义了一致性程度:锁定、数据流图和异常。通过现象(异常)定义隔离级别旨在允许SQL标准的非基于锁的实现。
本文展示了定义隔离级别的异常方法的一些弱点。这三种现象是不明确的。即使他们最广泛的解释也不排除异常行为。这导致了一些违反直觉的结果。特别是,基于锁的隔离级别具有与ANSI等效级别不同的特性。这令人不安,因为商业数据库系统通常使用锁定。此外,ANSI现象并不区分商业系统中流行的几种隔离级别。
第2节介绍了基本的隔离级别术语。它定义了ANSI SQL和锁定隔离级别。第3节研究了ANSI隔离级别的一些缺点,并提出了一种新现象。还定义了其他常用的隔离级别。ANSI SQL隔离级别和1977年在[GLPT]中定义的一致性程度之间映射了各种定义。它们还包括Date对光标稳定性和可重复读取[DAT]的定义。在统一的框架中讨论隔离级别可以减少混乱。第4节介绍了一种称为快照隔离的多版本并发控制机制,它避免了ANSI SQL现象,但不可序列化。快照隔离本身就很有趣,因为它提供了一种简化的隔离级别方法,介于提交读取和可重复读取之间。一种新的形式主义(可在本文的较长版本[OOBBGM]中找到)将多版本数据的降低隔离级别与经典的单版本锁定序列化理论联系起来。第5节探讨了一些新的异常情况,以区分第3节和第4节中介绍的隔离级别。这里提出的扩展ANSI SQL现象缺乏描述快照隔离和游标稳定性的能力。第6节是总结和结论。
**2.**隔离定义
2.1 可串行化概念
事务和锁定概念在文献[BHG、PAP、PON、GR]中有很好的记载。接下来的几段回顾这里使用的术语。
事务将数据库从一个一致状态转换为另一个一致状态的一组操作分组。历史将一组事务的交错执行建模为其操作的线性顺序,例如特定数据项的读写(即插入、更新和删除)。如果历史记录中的两个操作是由同一数据项上的不同事务执行的,并且其中至少有一个是写入操作,则称为冲突。在[EGLT]之后,此定义对“data item数据项”进行了广泛的解释:它可以是一个表的行、一个页面、整个表或队列上的消息。冲突操作也可能发生在谓词锁覆盖的一组数据项上以及单个数据项上。
特定的历史会产生一个依赖关系图,定义事务之间的时态数据流。历史记录中已提交事务的操作表示为图形节点。如果事务T1的操作op1与历史中事务T2的操作op2冲突并在其之前,则该对<op1,op2>将成为依赖关系图中的一条边。如果两个历史记录具有相同的提交事务和相同的依赖关系图,则它们是等效的。如果历史相当于一个串行历史,那么它是可串行化的——也就是说,如果它与某些按顺序一次执行一个事务的历史具有相同的依赖关系图(事务间时态数据流)。
2.2 ANSI SQL****隔离级别
ANSI SQL隔离设计人员寻求一个允许许多不同实现的定义,而不仅仅是锁定。
他们用以下三种现象来定义隔离:
**P1 (**脏读):
事务T1修改数据项。
然后,另一个事务T2在T1执行提交或回滚之前读取该数据项。
如果T1随后执行回滚,则T2读取的数据项从未提交过,因此也从未真正存在过。
**P2 (**不可重复或模糊读取):
事务T1读取一个数据项。
然后,另一个事务T2修改或删除该数据项并提交。
如果T1随后尝试重新读取该数据项,则会收到修改后的值或发现该数据项已被删除。
**P3 (**幻影):
事务T1读取满足某些<搜索条件>的一组数据项。
事务T2然后创建满足T1的<搜索条件>的数据项并提交。如果T1然后以相同的<搜索条件>重复其读取,它将获得一组与第一次读取不同的数据项。
这些现象都不可能在连续的历史中发生。因此,根据可串行化定理,它们不能出现在可串行化历史中[EGLT,BHG定理3.6,GR第7.5.8.2节,PON定理9.4.2]。
由读取、写入、提交和中止组成的历史记录可以用简写符号书写:
“w1[x]”表示事务1对数据项x的写入(即数据项的“修改”方式),“r2[x]”表示事务2对x的读取。
事务1读取和写入满足谓词P的一组记录分别由r1[P]和w1[P]表示。
事务1的提交和中止(回滚)分别写为“c1”和“a1”。
现象P1可能被重申为不允许以下情况:
(2.1) w1[x]…r2[x]…(a1 and c2 按任意顺序)
P1的英语陈述模棱两可。它实际上并不坚持T1中止;它只是说,如果发生这种情况,可能会发生不幸的事情。一些阅读P1的人将其理解为:
(2.2) w1[x]…r2[x]…((c1 or a1) and (c2 or a2) 按任意顺序)
禁止P1的(2.2)变体不允许任何历史记录,其中T1修改数据项x,然后T2在T1提交或中止之前读取数据项。它并不坚持T1中止或T2提交。
定义(2.2)对P1的解释要比(2.1)宽泛得多,因为它禁止事务T1和T2的所有四个可能的提交中止对,而(2.1)只禁止四个中的两个。如果将来可能出现异常情况,将(2.2)解释为P1的含义将禁止执行序列。我们称(2.2)为P1的广义解释,(2.1)为P1的严格解释。解释(2.2)规定了可能导致异常的现象,而(2.1)规定了实际异常。分别表示为P1和A1。因此:
P1: w1[x]…r2[x]…((c1 or a1) and (c2 or a2) 按任意顺序)
A1: w1[x]…r2[x]…(a1 and c2 in any order)
类似地,英语语言现象P2和P3具有严格和广泛的解释,并表示P2和P3为广泛,A2和A3为严格:
P2: r1[x]…w2[x]…((c1 or a1) and (c2 or a2) 按任意顺序)
A2: r1[x]…w2[x]…c2…r1[x]…c1
P3: r1[P]…w2[y in P]…((c1 or a1) and (c2 or a2) 按任意顺序)
A3: r1[P]…w2[y in P]…c2…r1[P]…c1
第3节在开发了更多的概念机制之后,分析了这些替代解释。它认为需要对这些现象进行广泛的解释。请注意,ANSI SQL P3的英语语句仅禁止插入谓词,但上面的P3故意禁止在读取谓词后影响满足谓词的元组的任何写入(插入、更新、删除)。
本文随后讨论了多值历史的概念(简称MV历史-见[BHG],第5章)。现在不必详细说明,多版本系统中可能同时存在一个数据项的多个版本。任何读取都必须明确说明正在读取的版本。有人试图将ANSI隔离定义与标准锁定调度器的多版本系统以及更常见的单版本系统联系起来。现象P1、P2和P3的英语陈述暗示了单一版本的历史。这就是我们在下一节中对它们的解释。
ANSI SQL通过"表1"的矩阵定义了四个隔离级别。每个隔离级别的特征是事务被禁止经历的现象(广义或严格的解释)。然而,ANSI SQL规范并没有仅根据这些现象定义可序列化隔离级别。[ANSI]中的子条款4.28“SQL事务”指出,可序列化隔离级别必须提供“通常称为完全可序列化执行”。与此附加条件相比,表的突出性导致了一种常见的误解,即不允许这三种现象意味着可序列化。"表1"调用了不允许这三种异常现象序列化的历史记录。
表1.根据三种原始现象定义的ANSI SQL隔离级别 | |||
---|---|---|---|
隔离级别 | P1 (or A1) 脏读 | P2 (or A2) 不可重复或模糊读 | P3 (or A3) 幻影 |
ANSI 读未提交 | 可能 | 可能 | 可能 |
ANSI 读已提交 | 不可能 | 可能 | 可能 |
ANSI 可重复读 | 不可能 | 不可能 | 可能 |
ANOMALY 可串行化 | 不可能 | 不可能 | 不可能 |
隔离级别由禁止其经历的现象定义。与严格的解释相比,对一种现象选择广义的解释排除了更多的历史。这意味着我们主张更严格的隔离级别(更多的历史记录将被禁止)。第3节显示,即使对P1、P2和P3进行广泛的解释,禁止这些现象也不能保证真正的可序列化性。在[ANSI]中,删除P3并只使用子条款4.28定义ansi serializable会更简单。请注意,表1不是最终结果;表3将取代它。
2.3 Locking 锁定
大多数SQL产品使用基于锁的隔离。因此,尽管出现了某些问题,但在锁定方面描述ANSI SQL隔离级别是有用的。
在锁定调度程序请求下执行的事务对其读取和写入的数据项或数据项集进行读取(共享)和写入(独占)锁定。如果至少有一个锁是写锁,则同一项上不同事务的两个锁将发生冲突。
给定<搜索条件>上的读(或写)谓词锁实际上是满足<搜索条件>的所有数据项上的锁。这可能是一个无限集infinite set。它包括数据库中存在的数据,以及当前不在数据库中但在插入或更新当前数据项以满足<搜索条件>时将满足谓词的任何虚拟数据项。在SQL术语中,谓词锁覆盖满足谓词的所有元组以及insert、update或delete语句可能导致满足谓词的任何元组。如果一个是写锁,并且两个锁都覆盖了一个(可能是幻象)数据项,则不同事务的两个谓词锁会发生冲突。项锁(记录锁)是谓词锁,谓词在其中命名特定记录。
如果事务在写入(读取)每个数据项或谓词之前请求对该数据项或谓词定义的一组数据项的写入(读取)锁定,则该事务具有格式良好的写入(读取)。如果事务具有格式良好的写入和读取,则该事务是格式良好的。如果事务在释放写(读)锁后未对数据项设置新的写(读)锁,则事务有两个阶段的写(读)。如果事务在释放某些锁后不请求任何新锁,则事务将显示两阶段锁定。
如果事务请求的锁一直保留到事务提交或中止之后,则其持续时间较长。否则,它们的持续时间很短。通常,短锁会在操作完成后立即释放。
如果一个事务持有一个锁,而另一个事务请求一个冲突的锁,那么在释放前一个事务的冲突锁之前,不会授予新的锁请求。
基本的序列化定理是,格式良好的两阶段锁定保证了可序列化性——在两阶段锁定下产生的每个历史都相当于一些串行历史。相反,如果一个事务不是格式良好的或分为两个阶段的,那么除了退化的情况外,不可序列化的执行历史记录是可能的[EGLT]。
[GLPT]论文定义了四个一致性度,试图展示锁定、依赖和基于异常的特征的等价性。异常定义(见定义1)过于模糊。作者们在定义[GR]的这一方面继续受到批评。只有历史、依赖关系图或锁定方面的数学定义经受住了时间的考验。
"表2"根据锁作用域(项或谓词)、模式(读或写)及其持续时间(短或长)定义了许多隔离类型。我们相信称为locking read uncommitted、locking read committed、locking repeatable read和locking serializable的隔离级别是ANSI SQL隔离级别所期望的锁定定义,但如下面所示,它们与"表1"中的定义大不相同。因此,有必要区分根据锁定义的隔离级别与基于ANSI SQL现象的隔离级别。为了进行区分,"表2"中的标高标有“Locking”前缀,而不是"表1"中的“ANSI”前缀。
表 2. 根据锁定义的一致性程度和锁定隔离级别。 | ||
---|---|---|
一致性 level = Locking 隔离级别 | 读锁 数据项和谓词 (除非另有说明,否则相同) | 写锁 数据项和谓词 (总是一样的) |
Degree 0 | 不需要 | Well-formed Writes |
Degree 1 = Locking 读未提交 | 不需要 | Well-formed Writes 长持续时间写锁 |
Degree 2 = Locking 读已提交 | Well-formed Reads 短时读锁(两个) | Well-formed Writes, 长持续时间写锁 |
游标稳定性 (see Section 4.1) | Well-formed Reads 读取当前游标上保持的锁 短时读谓词锁 | Well-formed Writes, 长持续时间写锁 |
Locking 可重复读 | 长持续时间数据项读取锁定 短时读谓词锁 | Well-formed Writes, 长持续时间写锁 |
Degree 3 = Locking 串行化 | Well-formed Reads 长持续时间读取锁(两个) | Well-formed Writes, 长持续时间写锁 |
[GLPT]定义了0度(Degree 0)一致性以允许脏读和写:它只需要操作原子性。度1、2和3分别对应于锁定读未提交、读已提交和可序列化。没有与锁定可重复读取隔离级别匹配的隔离级别。
Date和IBM最初使用名称“Repeatable Reads”[DAT,DB2]来表示可序列化或锁定可序列化。这似乎比[GLPT]术语“3级隔离”(Degree 3 isolation.)更容易理解。"可重复读取的ANSI SQL含义与Date的原始定义不同,我们觉得这个术语很不幸。由于ANSI SQL可重复读取隔离级别没有明确排除异常P3,因此从P3的定义中可以清楚地看出,读取是不可重复的!我们用锁定REPE重复这个术语的误用表2中的ATABLE与ANSI的定义相似。类似地,Date创造了术语游标稳定性,作为2级隔离的一个更容易理解的名称,并增加了对丢失游标更新的保护,如下文第4.1节所述。
释义隔离级别L1弱于隔离级别L2(或L2强于L1),表示为L1«L2,前提是所有遵循L2标准的非序列化历史也满足L1,并且至少有一个非序列化历史可以发生在级别L1,但不发生在级别L2。如果满足L1和L2的非序列化历史集相同,则两个隔离级别L1和L2是等效的,表示为L1L2。L1不强于L2,如果L1«L2或L1L2,则表示为L1«L2。两个隔离级别不可比较,表示为L1»«L2,当每个隔离级别允许另一个级别不允许的不可序列化历史时。
在比较隔离级别时,我们仅根据其中一个级别中可能出现的不可序列化历史来区分它们,而另一个级别中可能不会出现。两个隔离级别在允许的可序列化历史记录方面也可能有所不同,但我们称为Locking serializable==serializable,尽管众所周知,锁定调度程序不允许所有可能的可序列化历史记录。隔离级别可能是不切实际的,因为不允许太多可序列化的历史记录,但我们在这里不讨论这个问题。
这些定义产生以下评论。
备注1:
锁定读取未提交 Locking read uncommitted
« 锁定读取已提交Locking read committed
« 锁定可重复读取Locking repeatable read
« 可序列化锁定Locking serializable
在下一节中,我们将重点比较ANSI和锁定定义。
3. 分析ANSI SQL隔离级别
从正面开始,锁定隔离级别符合ANSI SQL要求
备注2:"表2"的锁定协议定义的锁定隔离级别至少与"表1"中相应的基于现象的隔离级别一样强。参见[OOBBGM]以获取证据。
因此,锁定隔离级别至少与相同命名的ANSI级别隔离。他们更孤立吗?答案是肯定的,即使是在最低水平。锁定未提交的读取提供了长时间的写入锁定,以避免所谓的“脏写”现象,但ANSI SQL不排除这种异常行为,ANSI serializable除外。脏写的定义如下:
P0 (脏写): 事务T1修改数据项。然后,另一个事务T2在T1执行提交或回滚之前进一步修改该数据项。如果T1或T2随后执行回滚,则不清楚正确的数据值应该是什么。对此的广义解释是:
P0: w1[x]…w2[x]…((c1 or a1) and (c2 or a2) 按任意顺序)
脏写不好的一个原因是它们会破坏数据库的一致性。假设x和y之间存在约束(例如,x=y),如果单独运行,T1和T2各自保持约束的一致性。但是,如果两个事务以不同的顺序写入x和y,则很容易违反该约束,而只有在存在脏写入时才会发生这种情况。
例如,考虑历史 w1[x] w2[x] w2[y] c2 w1[y] c1. T1变为y,T2变为x都“存活”“survive”. 如果T1在x和y中写入1,而T2在x和y中写入2,则结果将是x=2,y=1违反x=y。
正如[GLPT,BHG]和其他地方所讨论的,自动事务回滚是P0重要的另一个紧迫原因。如果没有P0的保护,系统无法通过在映像之前恢复来撤消更新。 考虑历史: w1[x] w2[x] a1. 您不希望通过恢复 w1[x] 在x before映像来撤消 w1[x],因为这将消除w2的更新。但是,如果您不恢复它的before映像,并且事务T2稍后中止,那么您也无法通过恢复它的before映像来撤消w2[x]!即使是最脆弱的锁定系统也拥有长时间的写锁。否则,它们的恢复系统将失败。因此,我们总结评论3:
**评论3:**应修改ANSI SQL隔离,以要求所有隔离级别均为P0。
我们现在认为需要对三种ANSI现象进行广泛的解释。回想一下,严格的解释是:
A1: w1[x]…r2[x]…(a1 and c2 按任意顺序) (脏读)
A2: r1[x]…w2[x]…c2…r1[x]…c1 (模糊或不可重复读)
A3: r1[P]…w2[y in P]…c2…r1[P]…c1 (幻影)
根据"表1",读取提交隔离下的历史禁止异常A1,可重复读取隔离禁止异常A1和A2,可序列化隔离禁止异常A1、A2和A3。考虑历史H1,涉及银行余额行X和Y之间的40美元转移:
H1: r1[x=50]w1[x=10]r2[x=10]r2[y=50]c2 r1[y=50]w1[y=90]c1
H1是不可序列化的,这是典型的不一致分析问题,事务T1将数量40从x传输到y,总余额保持在100,而T2读取的是不一致状态,总余额为60。历史H1不违反任何异常A1、A2或A3。对于A1,两个事务中的一个必须中止;对于A2,同一事务必须第二次读取数据项;A3需要虚值。所有这些事情都不会发生在H1中。而是考虑对A1的广义解释,P1现象:
P1: w1[x]…r2[x]…((c1 or a1) and (c2 or a2) 按任意顺序)
H1确实违反了P1。因此,我们应该将P1理解为ANSI的意图,而不是A1。广义的解释是正确的。类似的论点表明,P2应该被视为ANSI的意图,而不是A2。区分这两种解释的历史是:
H2: r1[x=50]r2[x=50]w2[x=10]r2[y=50]w2[y=90]c2r1[y=90]c1
H2 是不可序列化 — 这是另一个不一致的分析,T1的总余额为140。这一次,两个事务都不会读取脏数据(即未提交的数据)。因此满足P1。同样,不会读取两次数据项,也不会更改任何相关的谓词求值。H2的问题是,在T1读取y时,x的值已经过时。如果T2再次读取x,它就会被改变;但由于T2不能做到这一点,A2不适用。将A2替换为P2(更广泛的解释)解决了这个问题。
P2: r1[x]…w2[x]…((c1 or a1) and (c2 or a2) 按任意顺序)
当w2[x=20]覆盖r1[x=50]时,H2将被取消资格。最后,考虑A3和历史H3:
A3: r1[P]…w2[y in P]…c2…r1[P]…c1 (Phantom)
H3: r1[P] w2[insert y to P] r2[z] w2[z] c2 r1[z] c1
这里T1执行<搜索条件>以查找活动员工列表。然后T2插入一个新的活跃员工,然后更新z,即公司中的员工数量。接下来,T1读取在职员工的计数作为检查,并发现不符点。这个历史显然是不可序列化的,但是A3允许,因为没有谓词被计算两次。同样,广义解释解决了这个问题。
P3: r1[P]…w2[y in P]…((c1 or a1) and (c2 or a2) 按任意顺序)
如果禁止P3,则历史记录H3无效。这显然是ANSI的意图。上述讨论展示了以下结果。
**备注4:**严格的解释A1、A2和A3有意料之外的弱点。正确的解释是广义的。我们假设在接下来的内容中,ANSI的意思是定义P1、P2和P3。
**备注5:**ANSI SQL隔离现象不完整。仍然可能出现一些异常情况。必须定义新现象以完成锁定的定义。此外,P3必须重新表述。在以下定义中,我们删除对(c2或a2)的引用,这些引用不限制历史。
P0: w1[x]…w2[x]…(c1 or a1) (脏写)
P1: w1[x]…r2[x]…(c1 or a1) (脏读)
P2: r1[x]…w2[x]…(c1 or a1) (模糊或不可重复读)
P3: r1[P]…w2[y in P]…(c1 or a1) (幻影)
一个重要的注意事项是,ANSI SQL P3仅禁止插入(和更新,根据某些解释)谓词,而上述P3的定义禁止在读取谓词后进行任何满足谓词的写入-写入可以是插入、更新或删除。
"表3"给出了根据这些现象拟定的ANSI隔离等级的定义。
表3:根据四种现象定义的ANSI SQL隔离级别 | ||||
---|---|---|---|---|
隔离级别 | P0 脏写 | P1 脏读 | P2 模糊读 | P3 幻影 |
未提交读 | 不可能 | 可能 | 可能 | 可能 |
已提交读 | 不可能 | 不可能 | 可能 | 可能 |
可重复读 | 不可能 | 不可能 | 不可能 | 可能 |
可串行化 | 不可能 | 不可能 | 不可能 | 不可能 |
对于单版本历史,结果表明P0、P1、P2、P3现象是锁定的伪装版本。例如,禁止P0会阻止第二个事务在第一个事务写入项之后再写入该项,这相当于说数据项(和谓词)上持有长期写锁。因此,脏写在所有级别都是不可能的。类似地,禁止P1相当于对数据项进行格式良好的读取。禁止P2意味着对数据项的长期读取锁定。最后,禁止P3意味着长期读取谓词锁。因此,由这些现象定义的表3的隔离级别提供了与表2的锁定隔离级别相同的行为。
备注6:"表2"的锁定隔离级别和"表3"的唯象定义是等效的。换句话说,P0、P1、P2和P3是锁定行为的伪装重新定义。
在下面的内容中,我们将通过"表3"中的名称引用表3中列出的隔离级别,相当于"表2"中这些隔离级别的锁定版本。当我们提到ANSI 读未提交、ANSI 读已提交、ANSI 可重复读和anomaly 序列化时,我们指的是"表1"中的ANSI定义(不充分,因为它不包括P0)。
下一节将展示许多商用隔离实现提供的隔离级别介于读取提交和可重复读取之间。为了实现区分这些实现的有意义的隔离级别,我们将假定P0和P1为基础,然后添加区分新现象的选项。
4. 其他隔离类型
4.1 游标稳定性(也是个隔离级别)
游标稳定性旨在防止丢失更新现象。
P4 (****丢失更新): 当事务T1读取数据项,然后T2更新数据项(可能基于先前的读取),然后T1(基于其先前的读取值)更新数据项并提交时,就会发生丢失更新异常。就历史而言,这是:
P4: r1[x]…w2[x]…w1[x]…c1 (丢失更新)
如历史H4所示,问题在于即使T2提交,T2的更新也将丢失。
H4: r1[x=100] r2[x=100] w2[x=120] c2 w1[x=130] c1
x的最终值仅包含30乘以T1的增量。P4在读提交隔离级别是可能的,因为当禁止P0(执行第一次写入操作的事务的提交在第二次写入之前)或P1(需要在写入之后进行读取)时,H4是允许的。然而,禁止P2也排除了P4,因为w2[x]在r1[x]之后、T1提交或中止之前。因此,异常P4有助于区分提交读取和可重复读取之间强度中等的隔离级别。
游标稳定性隔离级别扩展了SQL游标的读提交锁定行为,方法是为从游标获取添加新的读操作,并要求在游标的当前项上保持锁。锁定将一直保持,直到光标移动或关闭(可能是通过提交)。自然地,抓取事务可以更新行,在这种情况下,在事务提交之前,即使在光标移动到后续抓取之后,该行仍将保持写锁。符号扩展为包括rc(表示读取游标)和wc(表示写入游标的当前记录)。rc1[x]和随后的wc1[x]排除了介入w2[x]。在这种情况下,现象P4(重命名为P4C)被阻止。
P4C: rc1[x]…w2[x]…w1[x]…c1 (丢失更新)
备注7:
已提交读 « 游标稳定性 « 可重复读
SQL系统广泛实现游标稳定性,以防止通过游标读取的行的更新丢失。在某些系统中,已提交读实际上是更强的游标稳定性。ANSI标准允许这样做。
将光标放在项目上以保持其值稳定的技术可用于多个项目,但代价是使用多个光标。因此,对于访问少量固定数量数据项的任何事务,程序员可以将游标稳定性发挥到有效锁定、可重复读取隔离的作用。然而,这种方法不方便,也不通用。因此,始终存在符合P4(当然还有更普遍的P2)现象的历史,而这些历史不会被光标稳定性排除。
4.2 快照隔离
这些讨论自然而然地提出了一种称为快照隔离的隔离级别,在该级别中,每个事务从事务启动时(提交的)数据的快照中读取数据,称为其启动时间戳。此时间可以是事务首次读取之前的任何时间。在快照隔离中运行的事务在尝试读取时不会被阻止,只要可以维护其开始时间戳中的快照数据。事务的写入(更新、插入和删除)也将反映在此快照中,如果事务第二次访问(即读取或更新)数据,将再次读取。事务开始时间戳之后活动的其他事务的更新对事务不可见。
快照隔离是一种多版本并发控制。它扩展了[BHG]中描述的多版本混合方法,该方法允许通过只读事务读取快照。
当事务T1准备提交时,它会获得一个提交时间戳,该时间戳大于任何现有的开始时间戳或提交时间戳。只有在T1的执行间隔[StartTimestamp,Commit Timestamp]中没有其他具有提交时间戳的事务T2写入T1也写入的数据时,事务才会成功提交。否则,T1将中止。此功能称为Firstcommitter wins,用于防止更新丢失(现象P4)。当T1提交时,其更改对于开始时间戳大于T1提交时间戳的所有事务都可见。
快照隔离是一种多版本(MV)方法,因此单值(SV)历史不能正确反映时间动作序列。在任何时候,每个数据项都可能有多个版本,由活动和提交的事务创建。事务读取必须选择适当的版本。在第3节的开头考虑历史H1,它显示了P1在单值执行中的需要。在快照隔离下,相同的操作序列将导致多值历史记录:
H1.SI: r1[x0=50] w1[x1=10] r2[x0=50] r2[y0=50] c2
r1[y0=50] w1[y1=90] c1
H1.SI 具有可序列化执行的数据流。在[OOBBGM]中,我们展示了所有快照隔离历史可以映射到单值历史,同时保留数据流依赖关系(MV历史被称为视图等价于SV历史,这是[BHG]第5章中介绍的方法)。例如,MV history H1.SI将映射到可序列化的SV history:
H1.SI.SV: r1[x=50] r1[y=50] r2[x=50] r2[y=50] c2
w1[x=10] w1[y=90] c1
MV历史到SV历史的映射是将快照隔离置于隔离层次结构中所需的唯一严格的试金石。
快照隔离是不可序列化的,因为事务的读取发生在一个瞬间,而写入发生在另一个瞬间。
例如,考虑单值历史:
H5: r1[x=50] r1[y=50] r2[x=50] r2[y=50] w1[y=-40]
w2[x=-40] c1 c2
H5是不可序列化的,具有与快照隔离下相同的事务间数据流(事务读取的版本没有选择)。在这里,我们假设为x和y写入新值的每个事务都应保持x+y应为正的约束,而T1和T2单独正常工作时,该约束在H5中无法保持。
约束冲突是一种常见且重要的并发异常类型。单个数据库满足对多个数据项的约束(例如,键的唯一性、引用完整性、两个表中行的复制等)。它们一起构成了数据库不变约束谓词C(DB)。如果数据库状态DB与约束一致,则不变量为true,否则为false。事务必须保留约束谓词以保持一致性:如果事务启动时数据库是一致的,则事务提交时数据库将是一致的。如果事务读取的数据库状态违反了约束谓词,则该事务会遇到约束违反并发异常。这种约束冲突在[DAT]中称为不一致分析。
**A5 (**数据项约束冲突): 假设C()是数据库中两个数据项x和y之间的数据库约束。这里有两个由约束冲突引起的异常。
A5A 读偏斜假设事务T1读取x,然后第二个事务T2将x和y更新为新值并提交。如果现在T1读取y,它可能会看到不一致的状态,因此产生不一致的状态作为输出。就历史而言,我们有异常情况:
A5A: r1[x]…w2[x]…w2[y]…c2…r1[y]…(c1 or a1) (读偏斜)
A5B 写偏斜: 假设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)(写偏斜)
模糊读 (P2) 是读倾斜的退化形式,其中x=y。更典型的是,事务读取两个不同但相关的项(例如,引用完整性)。写入偏差(A5B)可能由银行的约束引起,只要通常持有的余额之和保持非负,账户余额就可以变为负数,出现异常情况,如历史记录H5所示。
显然,在排除P2的历史记录中,A5A和A5B都不会出现,因为A5A和A5B都有T2写入以前由未提交T1读取的数据项。因此,现象A5A和A5B仅用于区分低于可重复读入强度的隔离级别。
ANSI SQL可重复读取的定义在其严格解释中捕获了行约束的退化形式,但忽略了一般概念。具体来说,表2的锁定可重复读取提供了防止行约束冲突的保护,但表1的ANSI SQL定义禁止异常A1和A2,却没有。
现在回到快照隔离,它异常强大,甚至比 已提交读 更强大。
备注 8: 已提交读 « 快照隔离
证明:在快照隔离中,first-committer-wins阻止P0(脏写),时间戳机制阻止P1(脏读),因此快照隔离并不比 已提交读 弱。此外,A5A在 已提交读 下是可能的,但在快照隔离时间戳机制下是不可能的。因此 已提交读 « 快照隔离。
注意,在单值解释中,很难描述快照隔离历史如何与现象P2不符。异常A2不会发生,因为快照隔离下的事务即使在另一个事务进行临时干预更新之后,也会读取数据项的相同值。然而,写入偏移(A5B)显然可以发生在快照隔离历史(例如H5)中,并且在我们一直在推理的单值历史解释中,禁止P2也会排除A5B。因此,快照隔离允许可重复读取不允许的历史异常。
快照隔离无法经历A3异常。在另一个事务更新后重新读取谓词的事务将始终看到相同的旧数据项集。但可重复读取隔离级别可能会出现A3异常。快照隔离历史禁止具有异常A3的历史,但允许A5B,而可重复读取则相反。因此:
备注 9: 可重复读 »« 快照隔离
但是,快照隔离并不排除P3。考虑一个约束,它表示由谓词确定的一组作业任务不能有大于8的小时数。T1读取该谓词,确定总和仅为7小时,并添加一个持续时间为1小时的新任务,而并发事务T2执行相同的操作。由于这两个事务插入不同的数据项(以及不同的索引项,如果有的话),第一提交者Wins并不排除这种情况,并且可以在快照隔离中发生。但在任何等效的序列历史中,P3现象都会在这种情况下出现。
也许最值得注意的是,快照隔离没有幻影(严格意义上的ANSI定义)。每个事务都不会看到并发事务的更新。因此,在没有[ANSI]第4.28子条款中的额外限制的情况下,可以声明以下令人惊讶的结果(回想一下,表1将异常可序列化定义为可序列化的ANSI SQL定义):
备注 10: 快照隔离历史记录排除异常A1、A2和A3。因此,在表1异常序列化的异常解释中:异常序列化«快照隔离。
快照隔离提供了使用非常旧的时间戳运行事务的自由,从而允许它们进行时间旅行(从数据库的历史角度来看),而不会阻塞或被写入阻塞。当然,如果使用非常旧的时间戳更新事务试图更新由最近的事务更新的任何数据项,则它们将中止。
快照隔离允许以Reed[REE]的工作为模型的简单实现。这种多版本数据库有几种商业实现。Borland的InterBase 4[THA]和Microsoft Exchange系统的引擎都提供了快照隔离,并具有First-committer-wins功能。First-committer- wins要求系统记住属于在每个活动事务的开始时间戳之后提交的任何事务的所有更新(写锁)。如果事务的更新与其他人记住的更新冲突,它将中止事务。
快照隔离的“乐观”并发控制方法对只读事务具有明显的并发优势,但对更新事务的好处仍存在争议。这可能不利于长时间运行的更新事务与高争用性短事务竞争,因为长时间运行的事务不太可能是它们编写的所有事务的第一个写入者,因此可能会被中止。(请注意,这种情况也会在锁定实现中造成实际问题,如果解决方案是不允许长时间运行的更新事务占用短事务锁,那么快照隔离也是可以接受的。)当然,在短更新事务冲突最小且长时间运行的事务可能是只读的情况下,快照隔离应该会提供良好的结果。在具有可比长度的交易之间存在高度争议的制度中,快照隔离提供了一种经典的乐观方法,对此方法的价值存在不同的意见。
4.3 其他多版本系统
还有其他多版本的模式。一些商业产品维护对象的版本,但将快照隔离限制为只读事务(例如,SQL-92、Rdb和其他一些数据库中的SET TRANSACTION read only[MS、HOB、ORA];Postgres和Illustra[STO、ILL]长期维护此类版本,并提供时间旅行查询)。其他允许更新事务,但不提供第一提交者wins保护(例如,Oracle读取一致性隔离[ORA])。
Oracle读取一致性隔离为每条SQL语句提供语句开始时提交的最新数据库值。这就好像事务的开始时间戳在每个SQL语句中都是提前的。光标集的成员在打开光标时为。底层机制从语句时间戳开始重新计算行的适当版本。行插入、更新和删除由写锁覆盖,以提供第一个写入程序wins而不是第一个提交程序wins策略。读取一致性比读取提交(它不允许游标丢失更新(P4C))更强,但允许不可重复读取(P3)、常规丢失更新(P4)和读取倾斜(A5A)。快照隔离不允许P4或A5A。
如果仔细看一下SQL标准,它会将每条语句定义为原子语句。它在每个语句的开头都有一个可序列化的子事务(或时间戳)。我们可以想象一个隔离级别的层次结构,它是通过以有趣的方式将时间戳分配给语句来定义的(例如,在Oracle中,游标提取将游标的时间戳打开)。
5. 摘要和结论
总之,隔离级别的原始ANSI SQL定义存在严重问题(如第3节所述)。英语中的定义模棱两可且不完整。不排除脏写入(P0)。备注5是我们建议清理ANSI隔离等级,使其等同于[GLPT]的锁定隔离等级。
ANSI SQL旨在定义可重复读取隔离,以排除除幻影之外的所有异常。表1的异常定义并没有实现这一目标,但表2的锁定定义实现了这一目标。ANSI对术语可重复读取的选择是双重不幸的:(1)可重复读取不会给出可重复的结果,(2)行业已经使用了该术语的确切含义:可重复读取意味着可在多个产品中序列化。我们建议为此找到另一个术语。
许多商业上流行的隔离级别,在强度上介于表3的可重复读取和可串行化级别之间,在第4节中有一些新现象和异常。图2和表4显示了此处命名的所有隔离级别的特征。"图形2"中较高级别的隔离级别强度较高(见第4.1节开头的定义),连接线标有区分它们的现象和异常。
从积极的方面来看,多版本系统的降低隔离级别以前从未被描述过——尽管已经在多个产品中实现了。许多应用程序通过使用游标稳定性或Oracle的读取一致性隔离来避免锁争用。这样的应用程序会发现快照隔离比这两种方法都表现得更好:它避免了丢失的更新异常、一些幻象异常(例如,由ANSI SQL定义的异常)、它从不阻止只读事务,并且读卡器不阻止更新。
**表4:**允许以可能的异常为特征的隔离类型。 | ||||||||
---|---|---|---|---|---|---|---|---|
隔离级别 | P0 脏写 | P1 脏读 | P4C Cursor Lost Update | P4 Lost Update | P2 模糊读 | P3 幻影 | A5A 读偏斜 | A5B 写偏斜 |
未提交读 == Degree 1 | 不可能 | 可能 | 可能 | 可能 | 可能 | 可能 | 可能 | 可能 |
已提交读 == Degree 2 | 不可能 | 不可能 | 可能 | 可能 | 可能 | 可能 | 可能 | 可能 |
游标稳定性 | 不可能 | 不可能 | 不可能 | 有时可能 | 有时可能 | 可能 | 可能 | 有时可能 |
可重复读 | 不可能 | 不可能 | 不可能 | 不可能 | 不可能 | 可能 | 不可能 | 不可能 |
快照 | 不可能 | 不可能 | 不可能 | 不可能 | 不可能 | 有时可能 | 不可能 | 可能 |
ANSI SQL SERIALIZABLE == Degree 3 == Repeatable Read Date, IBM, Tandem, … | 不可能 | 不可能 | 不可能 | 不可能 | 不可能 | 不可能 | 不可能 | 不可能 |
图形 2: 隔离级别及其关系图。假设ANSI SQL隔离级别已得到加强,以符合备注5和表3的建议。
边缘用区分隔离级别的现象进行注释。未显示的是一个潜在的多版本层次结构,它通过在每条语
句的基础上选取读取时间戳,将快照隔离扩展到较低的隔离度。它也没有显示基于现象P1、P2和
P3的严格解释的原始ANSI SQL隔离级别。