淺談數據庫事務隔離發展歷史
事務隔離是數據庫系統設計中根本的組成部分,本文主要從標準層面來討論隔離級別的發展歷史,首先明確隔離級別劃分的目標;之后概述其否定之否定的發展歷程;進而引出 Adya給出的比較合理的隔離級別定義,最終總結隔離標準一路走來的思路。
目標
事務隔離是事務并發產生的直接需求,最直觀的、保證正確性的隔離方式,顯然是讓并發的事務依次執行,或是看起來像是依次執行。但在真實的場景中,有時并不需要如此高的正確性保證,因此希望犧牲一些正確性來提高整體性能。通過區別不同強度的隔離級別使得使用者可以在正確性和性能上自由權衡。隨著數據庫產品數量以及使用場景的膨脹,帶來了各種隔離級別選擇的混亂,數據庫的眾多設計者和使用者亟需一個對隔離級別劃分的共識,這就是標準出現的意義。一個好的隔離級別定義有如下兩個重要的目標:
正確:每個級別的定義,應該能夠將所有損害該級別想要保證的正確性的情況排除在外。也就是說,只要實現滿足某一隔離級別定義,就一定能獲得對應的正確性保證。 實現無關:常見的并發控制的實現方式包括,鎖、OCC以及多版本 。而一個好的標準不應該限制其實現方式。ANSI SQL標準(1992):基于異象
1992年ANSI首先嘗試指定統一的隔離級別標準,其定義了不同級別的異象(phenomenas), 并依據能避免多少異象來劃分隔離標準。異象包括:
臟讀(Dirty Read): 讀到了其他事務還未提交的數據;不可重復讀(Non-Repeatable/Fuzzy Read):由于其他事務的修改或刪除,對某數據的兩次讀取結果不同;幻讀(Phantom Read):由于其他事務的修改,增加或刪除,導致Range的結果失效(如where 條件查詢)。通過阻止不同的異象發生,得到了四種不同級別的隔離標準:

ANSI SQL標準看起來是非常直觀的劃分方式,不想要什么就排除什么,并且做到了實現無關。然而,現實并不像想象美好。因為它并不正確。
A Critique of ANSI(1995):基于鎖
幾年后,微軟的研究員們在A Critique of ANSI SQL Isolation Levels一文中對ANSI的標準進行了批判,指出其存在兩個致命的問題:
1,不完整,缺少對Dirty Write的排除
ANSI SQL標準中所有的隔離級別都沒有將Dirty Write這種異象排除在外,所謂Dirty Write指的是兩個未提交的事務先后對同一個對象進行了修改。而Dirty Write之所以是一種異象,主要因為他會導致下面的一致性問題:
H0: w1[x] w2[x] w2[y] c2 w1[y] c1
這段歷史中,假設有相關性約束x=y,T1嘗試將二者都修改為1,T2嘗試將二者都修改為2,順序執行的結果應該是二者都為1或者都為2,但由于Dirty Write的發生,最終結果變為x=2,y=1,不一致。
2,歧義
ANSI SQL的英文表述有歧義。以Phantom為例,如下圖歷史H3:
H3:r1[P] w2[insert y to P] r2[z] w2[z] c2 r1[z] c1
假設T1根據條件P查詢所有的雇員列表,之后T2增加了一個雇員并增加了雇員人數值z,之后T1讀取雇員人數z,最終T1的列表中的人數比z少,不一致。但T1并沒有在T2修改鏈表后再使用P中的值,是否就不屬于ANSI中對Phantom的定義了呢?這也導致了對ANSI的表述可能有嚴格和寬松兩種解讀。對于Read Dirty和Non-Repeatable/Fuzzy Read也有同樣的問題。
那么,如何解決上述兩個問題呢?Critique of ANSI的答案是:寧可錯殺三千,不可放過一個,即給ANSI標準中的異象最嚴格的定義。Critique of ANSI改造了異象的定義:
P0: w1[x]…w2[x]…(c1 or a1) (Dirty Write)
P1: w1[x]…r2[x]…(c1 or a1) (Dirty Read)
P2: r1[x]…w2[x]…(c1 or a1) (Fuzzy or Non-Repeatable Read)
P3: r1[P]…w2[y in P]…(c1 or a1) (Phantom)
此時定義已經很嚴格了,直接阻止了對應的讀寫組合順序。仔細可以看出,此時得到的其實就是基于鎖的定義:
Read Uncommitted,阻止P0:整個事務階段對x加長寫鎖Read Commited,阻止P0,P1:短讀鎖 + 長寫鎖Repeatable Read,阻止P0,P1,P2:長讀鎖 + 短謂詞鎖 + 長寫鎖Serializable,阻止P0,P1,P2,P3:長讀鎖 + 長謂詞鎖 + 長寫鎖問題本質
可以看出,這種方式的隔離性定義保證了正確性,但卻產生了依賴實現方式的問題:太過嚴格的隔離性定義,阻止了Optimize或Multi-version的實現方式中的一些正常的情況:
針對P0:Optimize的實現方式可能會讓多個事務各自寫自己的本地副本,提交的時候只要順序合適是可以成功的,只在需要的時候才abort,但這種選擇被P0阻止;針對P2:只要T1沒有在讀x,后續沒有與x相關的操作,且先于T2提交。在Optimize的實現中是可以接受的,卻被P2阻止?;貞汣ritique of ANSI中指出的ANSI標準問題,包括Dirty Write和歧義,其實都是由于多Object之間有相互約束關系導致的,如下圖所示,圖中黑色部分表示的是ANSI中針對某一個異象描述的異常情況,灰色部分由于多Object約束導致的異常部分,但這部分在傳統的異象定義方式中并不能描述,因此其只能退而求其次,擴大限制的范圍到黃色部分,從而限制了正常的情況。

由此,可以看出問題的本質:由于異象的描述只針對單個object,缺少描述多object之間的約束關系,導致需要用鎖的方式來作出超出必須的限制。相應地,解決問題的關鍵:要有新的定義異象的模型,使之能精準的描述多object之間的約束關系,從而使得我們能夠精準地限制上述灰色部分,而將黃色的部分解放出來。Adya給出的答案是序列化圖。
A Generalized Theory(1999):基于序列化圖
Adya在Weak Consistency: A Generalized Theory and Optimistic Implementations for Distributed Transactions中給出了基于序列化圖得定義,思路為先定義沖突關系;并以沖突關系為有向邊形成序列化圖;再以圖中的環類型定義不同的異象;最后通過阻止不同的異象來定義隔離級別。
序列化圖(Direct Serialization Graph, DSG)
序列化圖是用有向圖的方式來表示事務相互之間的依賴關系,圖中每個節點表示一個事務,有向邊表示存在一種依賴關系,事務需要等到所有指向其的事務先行提交,如下圖所示歷史的合法的提交順序應該為:T1,T2,T3:

這里的有向邊包括三種情況:
- 寫寫沖突ww(Directly Write-Depends):表示兩個事務先后修改同一個數據庫Object(w1[x]…w2[x]...); 先寫后讀沖突wr(Directly Read-Depends):一個事務修改某個數據庫Object后,另一個對該Object進行讀操作(w1[x]…r2[x]...); 先讀后寫沖突rw(Directly Anti-Depends):一個事務讀取某個Object或者某個Range后,另一個事務進行了修改(r1[x]…w2[x]… or r1[P]…w2[y in P]);
- 基于序列化圖的異象定義:
- 根據有向圖的定義,我們可以將事務對不同Object的依賴關系表示到一張同一張圖中,而所謂異象就是在圖中找不到一個正確的序列化順序,即存在某種環。而這種基于環的定義其實就是將基于Lock定義的異象最小化到圖中灰色部分:
- 1,P0(Dirty Write) 最小化為 G0(Write Cycles):序列化圖中包含兩條邊都為ww沖突組成的環,如H0:
H0: w1[x] w2[x] w2[y] c2 w1[y] c1
- 可以看出T1在x上與T2寫寫沖突,T2又在y上與T1寫寫沖突,形成了如下圖所示的環。

- 2,P1(Dirty Read) 最小化為 G1:Dirty Read異象的最小集包括三個部分G1a(Aborted Reads),讀到的uncommitted數據最終被abort;G1b(Intermediate Reads) :讀到其他事務中間版本的數據;以及G1c(Circular Information Flow):DSG中包含ww沖突和wr沖突形成的環。
- 3,P2(Fuzzy or Non-Repeatable Read) 最小化為 G2-item(Item Anti-dependency Cycles) :DSG中包含環,且其中至少有一條關于某個object的rw沖突
- 4,P3(Phantom) 最小化為 G2(Anti-dependency Cycles): DSG中包含環,并且其中至少有一條是rw沖突,仍然以上面的H3為例:
H3:r1[P] w2[insert y to P] r2[z] w2[z] c2 r1[z] c1
- T1在謂詞P上與T2 rw沖突,反過來T2又在z上與T1wr沖突,如下圖所示:

- 對應的隔離級別:
- 通過上面的討論可以看出,通過環的方式我們成功最小化了異象的限制范圍,那么排除這些異象就得到了更寬松的,通用的隔離級別定義:
PL-1(Read Uncommitted):阻止G0PL-2(Read Commited):阻止G1PL-2.99(Repeatable Read):阻止G1,G2-itemPL-3(Serializable):阻止G1,G2其他隔離級別:
- 除了上述的隔離級別外,在正確性的頻譜中還有著大量空白,也就存在著各種其他隔離級別的空間,商業數據庫的實現中有兩個比較常見:
- 1,Cursor Stability
- 該隔離界別介于Read Committed和Repeatable Read之間,通過對游標加鎖而不是對object加讀鎖的方式避免了Lost Write異象。
- 2, Snapshot Ioslation
- 事務開始的時候拿一個Start-Timestamp的snapshot,所有的操作都在這個snapshot上做,當commit的時候拿Commit-Timestamp,檢查所有有沖突的值不能再[Start- Timestamp, Commit-Timestamp]被提交,否則abort。長久以來,Snapshot Ioslation一直被認為是Serializable,但其實Snapshot Ioslation下還會出現Write Skew的異象。之后的文章會詳細介紹如何從Snapshot Ioslation出發獲得Serializable。
總結
- 對于事務隔離級別的標準,數據庫的前輩們進行了長久的探索:
ANSI isolation levels定義了異象標準,并根據所排除的異象,定義了,Read Uncommitted、Read Committed、Repeatable Read、Serializable四個隔離級別; A Critique of ANSI SQL Isolation Levels認為ANSI的定義并沒將有多object約束的異象排除在外,并選擇用更嚴格的基于Lock的定義擴大了每個級別限制的范圍; Weak Consistency: A Generalized Theory and Optimistic Implementations for Distributed Transactions認為基于Lock的定義過多的擴大了限制的范圍,導致正常情況被排除在外,從而限制了Optimize類型并行控制的使用;指出解決該問題的關鍵是要有模型能準確地描述這種多Object約束;并給出了基于序列化圖的定義方式,將每個級別限制的范圍最小化。參考
- A History of Transaction Histories
- ANSI isolation levels
- A Critique of ANSI SQL Isolation Levels
- Weak Consistency: A Generalized Theory and Optimistic Implementations for Distributed Transactions
- Generalized Isolation Level Definitions