數據庫隔離級別發展史

前言

提到數據庫隔離級別,就要提到數據庫事務的ACID 原則。ACID,即Atomicity, Consistency, Isolation, and Durability 是1983 被 Haerder, T. 和Reuter, A. 第一次在論文 “Principles of transaction-oriented database recovery” 提出,論文詳見鏈接,具體解釋如下:

  • Atomicity,原子性,簡單來說就是事務是一個不可分割的操作,要麼全部發生,要麼全部不發生。
  • Consistency,一致性,事務開始前和結束後,數據的完整性(約束)不會被破壞。這個一致性初次接觸可能難以理解,舉個經典的例子,把“A 賬號餘額有100元,B賬號餘額有0元,A轉賬給B賬號100元”作爲一個事務,則事務開始前和結束後,兩個賬號的餘額總和爲100元。
  • Isolation,隔離性,一個事務不影響其它事務。
  • Durability,持久性,事務所所作的數據更改會持久化到數據庫中。

其中,對於有些事務是會操作相同的數據集,如果對這部分事務進行完全隔離,則只能夠串行化運行事務。在很多系統中,這基本是不能接受的。爲了提高事務的處理能力,對事務逐漸定義出了不同的隔離級別,這也就引出了本文的主角——數據庫隔離級別。

隨着數據庫產品數量以及使用場景的膨脹,帶來了各種隔離級別選擇的混亂,數據庫的衆多設計者和使用者亟需一個對隔離級別劃分的共識,這就是標準出現的意義。而本文就是從標準的發展史爲時間線來記錄數據庫隔離級別的發展史。

一個好的隔離級別定義有如下兩個重要的目標:

  • 正確:只要實現滿足某一隔離級別定義,就一定能獲得對應的正確性保證。
  • 實現無關:常見的併發控制的實現方式包括,鎖、OCC以及多版本 。而一個好的標準不應該限制其實現方式。

ANSI SQL標準(1992):基於異象

1992年ANSI首先嚐試指定統一的隔離級別標準,其定義了不同級別的異象(phenomenas), 並依據能避免多少異象來劃分隔離標準。異象包括:

  • 髒讀(Dirty Read): 讀到了其他事務還未提交的數據;
  • 不可重複讀(Non-Repeatable/Fuzzy Read):由於其他事務的修改或刪除,對某數據的兩次讀取結果不同;
  • 幻讀(Phantom Read):由於其他事務的修改,增加或刪除,導致Range的結果失效(如where 條件查詢)。

通過阻止不同的異象發生,得到了四種不同級別的隔離標準:

ANSI Define 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阻止。

回憶Critique of ANSI中指出的ANSI標準問題,包括Dirty Write和歧義,其實都是由於多Object之間有相互約束關係導致的,如下圖所示,圖中黑色部分表示的是ANSI中針對某一個異象描述的異常情況,灰色部分由於多Object約束導致的異常部分,但這部分在傳統的異象定義方式中並不能描述,因此其只能退而求其次,擴大限制的範圍到黃色部分,從而限制了正常的情況。:

Isolation Cover

由此,可以看出問題的本質由於異象的描述只針對單個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:

GSG

這裏的有向邊包括三種情況:

  • 寫寫衝突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]);

GSG Edge

基於序列化圖的異象定義:

根據有向圖的定義,我們可以將事務對不同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寫寫衝突,形成了如下圖所示的環。

Write Cycle

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衝突,如下圖所示:

Anti-dependency Cycles

對應的隔離級別:

通過上面的討論可以看出,通過環的方式我們成功最小化了異象的限制範圍,那麼排除這些異象就得到了更寬鬆的,通用的隔離級別定義:

  • PL-1(Read Uncommitted):阻止G0
  • PL-2(Read Commited):阻止G1
  • PL-2.99(Repeatable Read):阻止G1,G2-item
  • PL-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 下的Write Skew異象,如下圖:
寫偏序

3,Serializable Snapshot Isolation

可串行化快照隔離(serializable snapshot isolation或SSI)是在快照隔離級別之上,支持串行化。

如果是事務1讀x寫y,事務2是讀y寫z會不會有write skew問題呢?不會。下面執行結果是等價於事務2先執行,然後事務1執行的串行順序的。

r1x r2y w1y w2z c1 c2
r2y w2z c2 r1x w1y c2

仔細想一下,單獨的讀x寫y,讀y寫x都可以,但兩者一起,就出問題了。總結出來,快照隔離存在的write skew的問題,本質上需要至少兩個條件:

  • 有讀寫衝突
  • 依賴成環
    如果可以破壞這些條件,就可以避免write skew,達到可串行化了。所以要做的就是檢測不同事務之間的讀寫依賴是否形成環。x的值依賴於事務2,而被事務1依賴,y的值依賴於事務1,成環了。學過數據結構我們知道,檢測圖中存在環我們可以使用深度優先遍歷遇到之前走過的結點。但是對性能有一定的影響。可以做一個簡化的措施,允許誤判以換取性能。

實現是這樣子的,爲每個值維護一個出邊和一個入邊。如果某個值即存在出邊又存在入邊,則可能是存在環的。我們寧可錯殺一千,不要放過一個,事務操作中,只要檢測到這種情況,就abort掉。還是看上面的例子:

r1x r2y w1y w2x c1 c2

事務2讀y,y被事務2依賴,於是在y的值設置出邊。執行到w1y時,事務1寫y,於是y依賴於事務1,設置y的入邊。這時我們發現y的入邊出邊都設置了,有潛在成環的可能性。於是讓abort掉事務1。

這裏說的是可串行化快照實現方式之一。還有一些其它的方式也可以實現,本質上都是要對讀寫衝突進行處理。對於OLTP類型的業務,大部分的流量都是在讀操作,而讀是不會被abort掉的,所以這類場合,實現可串行化快照隔離引入的性能開銷可以接受。

總結

對於事務隔離級別的標準,數據庫的前輩們進行了長久的探索:

  • 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章