引言
前一篇文章中,介紹了ANSI SQL標準下的事務隔離級別及其擴充套件,這篇文章主要討論了基於加鎖的方式如何實現不同的事務隔離級別,全文的組織架構如下:
- ANSI SQL標準下的事務隔離級別及其擴充套件回顧
- 基於加鎖方式的事務隔離原理
本文同步釋出在個人部落格oserror.com。
ANSI SQL標準下的事務隔離級別及其擴充套件回顧
ANSI SQL標準下的事務隔離級別是基於禁止某些干擾現象而制定的,這些現象如下:
髒讀P1
W1(X)…R2(X)…A1…R2(X)
不可重複讀P2
R1(X)…W2(X)…C2…R1(X)
幻讀P3
R1(P)…W2(P)…C2…R1(P)
針對三種現象,ANSI SQL標準設定了四種事務隔離級別,如下:
- Read Uncommitted:有可能發生P1,P2和P3
- Read Committed:不可能發生P1,有可能發生P2和P3
- Repeatable Read:不可能發生P1,P2,有可能發生P3
- Serializable:不可能發生P1,P2和P3
整個事務個隔離級別,與杜絕的現象的對應關係如下圖:
由於ANSI SQL的標準存在以下限制:
- 沒有提及寫操作的隔離性
- ANSI SQL的標準比較老,對於採用多版本併發控制實現隔離性的級別不能夠很好的描述
即新干擾現象P0和P4,其中髒寫P0如下
W1(X)…W2(X)…A1
寫丟失P4如下
R1(X)…R2(X)…W2(X)…C2…W1(X)…C1
因此,引入了新的隔離級別,包括
- Cursor Stability
- Snapshot
具體的分析,請參照我的博文(事務隔離(一):ANSI SQL事務隔離級別,限制及擴充套件)。
基於加鎖方式的事務隔離原理
基本概念
鎖有兩種,即共享鎖(Share Lock)和排他鎖(Exclusive Lock),對於不同事務加在同一個資料項上的鎖,如果其中至少有一個是排他鎖的話,那麼事務是會衝突的,即其中一個事務必須等待。一般共享鎖也稱為讀鎖(Read Lock),而排它鎖也稱為寫鎖(Write Lock)。
讀寫鎖根據鎖住的資料項不同,分為普通鎖和謂詞鎖。謂詞鎖是指鎖住滿足某一查詢條件的所有資料項,它不僅包括當前在資料庫中滿足條件的記錄,也包括即將要插入,更新或刪除到資料庫並滿足查詢條件的資料項。對於不同事務加在查詢條件下的謂詞鎖,在至少一個是寫鎖的情況,且兩個謂詞條件中包含重疊的資料項時,則兩個事務是衝突的。
well-formed read(write)是指在read(write)一個資料項或者查詢條件時,會先對資料項或者查詢條件加read(write) lock。如果一個事務的所有讀寫都是well-formed,那稱事務是well-formed。
two-phase read(write)是當有一個或多個read(write) lock被釋放後,不能再加新的read(write) lock。如果一個事務在釋放一個或多個鎖後,不再加其他的鎖,那麼稱該事務是two-phase的。
如果一把鎖從加上,到事務結束(commit or abort)後才釋放,則稱此鎖是long duration的,否則,稱鎖是short duration的。
多個併發執行的事務是serializability,指的是併發排程執行的結果等於這些事務某個序列執行的結果。例如,有三個併發執行的事務T1,T2和T3,如果其執行結果和其某個序列執行((T1,T2,T3),(T1,T3,T2),(T2,T1,T3),(T2,T3,T1),(T3,T1,T2),(T3,T2,T1))的結果相同。
如果一個事務T1持有一把鎖的情況下,另一個事務T2申請一把衝突的鎖,那麼,事務T2只有等到事務T1釋放這把鎖之後,才能加上這把鎖。
根據資料庫的基礎理論,採用well-formed two-phase locking方式排程事務的話,是能夠保證serializability的。
在瞭解到鎖的基本概念之後,接下來討論,如何基於鎖來實現各種不同的隔離級別。
隔離級別如何實現
先來看所有的干擾現象:
髒寫P0
W1(X)…W2(X)…A1
髒讀P1
W1(X)…R2(X)…A1…R2(X)
不可重複讀P2
R1(X)…W2(X)…C2…R1(X)
幻讀P3
R1(P)…W2(P)…C2…R1(P)
寫丟失P4
R1(X)…R2(X)…W2(X)…C2…W1(X)…C1
如果需要禁止P0,即禁止多個事務同時能修改一個資料項或謂詞條件,則需要修改資料時,加寫鎖,並且是long duration的,此時,隔離級別滿足Read Uncommitted。
如果需要禁止P1,即禁止讀取到其他事務修改的中間狀態的資料,在禁止P0的條件下,則需要,對讀加鎖,short duration的就能夠滿足條件,此時,隔離級別滿足Read Committed。
如果需要禁止P4,即禁止事務讀取並且修改某個資料項後,需要禁止其他事務再次修改,但如果只是讀取的話,不影響,這裡,需要一種特殊的鎖,稱為Cursor Lock,會對事務當前處理的行進行加鎖,如果行記錄被修改,那麼鎖會是long duration的,直到事務結束,如果,行未被修改,則鎖會提前被釋放,此時,隔離級別滿足Curstor stability。
如果需要禁止P2,即要禁止到讀到某個資料項後,該資料還可能被其他事務修改,因此,需要對讀加鎖,且一直加鎖到事務結束,即long duration,此時,隔離級別滿足Repeatable Read。
如果需要禁止P3,即要禁止讀到某個謂詞條件後,滿足該謂詞條件的資料還被其他事務修改,因此,需要對謂詞條件加讀鎖,且是long duration的,此時,隔離級別滿足SERIALIZABLE。
不同的加鎖與事務隔離級別的對應關係如下:
表格中最後一項中,對於讀寫鎖都是long duration的,即到事務結束才會釋放鎖,即事務過程中只有加鎖階段,沒有解鎖階段,這種方式和普通的two-phase locking有什麼區別呢?
Two-phase Locking
普通的two-phase locking包含兩個階段:
- Expanding phase:加鎖階段,此階段只加鎖,不釋放鎖
- Shrinking phase:解鎖階段,此階段只解鎖,不加鎖
普通的two-phase locking可能會如下問題:
假設有兩個事務T1,T2,它們的時序如下
T1 T2
R1(X)
W1(X)
R2(X)
W2(X)
A1複製程式碼
由於事務T2讀到的資料是事務T1修改的X,當事務T1回滾時,事務T2讀到的資料就是髒資料,因此,需要對事務T2也進行回滾,如果存在T3也讀到了T2修改的資料,那麼T3也需要回滾,這樣,會導致一系列的事務都需要回滾,稱為Cascading Aborts。
而表格中的two-phase locking,只有加鎖階段,因此,不會存在上述問題。只有加鎖階段的two-phase locking,也稱為strict two-phase locking。
由於two-phase locking採用的是加鎖的方式,因此有可能會碰到經典的死鎖問題,舉個例子,如下:
假設有事務T1,T2,它們的加鎖時序如下:
T1 T2
R1(X) R2(Y)
W1(Y) W2(X)複製程式碼
按照如上的時序,事務T1和T2處於等待互相釋放鎖的狀態,即死鎖。死鎖問題會導致事務無法繼續進行有效的工作,因此,必須要解決,常見的解決方案有:
- 死鎖檢測並消除
- 鎖等待一段時間閾值後,對事務進行回滾,並釋放所有鎖
第一種方法,死鎖檢測並消除的方法是有一個單獨的執行緒檢測事務的鎖等待圖,如果圖構成了一個環,那麼,說明發生了死鎖,此時,需要選擇環中的一個事務進行回滾,並釋放鎖,使得其他事務能夠繼續執行下去。
第二種方法,鎖等待一段時間閾值後,對事務進行回滾,並釋放其所有鎖,表明,每次發生死鎖時,都會先回滾最早開始執行的事務,使得其他的事務能夠繼續執行下去。
PS:
本部落格更新會在第一時間推送到微信公眾號,歡迎大家關注。