「分散式技術專題」併發系列一:基於加鎖的併發控制

Hubble資料庫發表於2023-02-14

併發控制分類

資料庫中如果出現了衝突,一般有幾種情況。兩個操作同時訪問了相同的資料庫條目, 並且他們中的任意一個是寫操作,那麼它們就是衝突的。

眾所周知,讀操作是不會相互衝突的,衝突的型別只有兩種:讀 -寫(或寫-讀)和寫-寫衝突, 並且無論這兩個操作是屬於相同的事務還是屬於不同的事務。

直觀上,兩個操作相沖突表明它們之間的執行順序很重要,而兩個讀操作的順序不重要。

從可序列化的角度來看的話,我們只需要處理事務中相互衝突的操作,而不是所有的操作。

為了實現併發,需要加入併發控制來避免衝突,一般的分類方式是基於同步原語的,可以依據它將併發控制分為兩類:基於對共享資料進行互斥訪問的(基於鎖 ),以及基於將事務的執行根據一系列規則進行排序的(基於協議)。

另一個視角來看,就是悲觀視角和樂觀視角,悲觀視角假設很多事務都會相互衝突, 而樂觀視角則假設並沒有太多的事務會相互衝突。

悲觀視角會在併發事務執行週期的早期對它們進行同步;而樂觀視角則會將這種同步推遲到事務提交之後。併發控制機制的總體分類如圖所示:


concurrent1
在本文中主要討論基於加鎖的併發控制。

基於加鎖的併發控制的基本思想是:如果多個衝突的操作會訪問同一個資料項, 那麼就保證這個資料項一次只被其中的一個訪問。這可以由為每個鎖單位分配一個鎖來實現。一個鎖會在開始訪問事務的時候被設定,然後在使用過後重置。顯然,如果一個鎖單位已經被一個操作鎖住,那麼其他操作就不能訪問它了。因此,當且僅當鎖未被別的事務佔用的時候,一個事務才能申領到鎖。

由於要做的事是同步衝突事務中的衝突操作,因此將分配給每個鎖單位的鎖分為兩種型別(鎖模式):讀鎖( read lock)和寫鎖(write lock)。如果兩個事務可以同時申請到鎖,說明兩個鎖是相容的。就如前邊提到的一樣,所有的讀鎖都相容,讀-寫和寫-寫鎖不相容。因此, 兩個事務可以併發的讀取同一資料項。

鎖定義

分散式資料庫會承擔事務的鎖管理的任務。在基於加鎖的系統中,排程程式是一個鎖管理程式。


concurrent2
如圖,首先事務管理程式將資料庫操作(讀或者寫)傳遞給鎖管理器,並將相應的資訊關聯起來 (如被訪問的資料項、發起資料庫操作的事務標誌符等)。

然後, 鎖管理程式檢查包含資料的鎖單位是否已經上鎖。如果已經上鎖,並且鎖模式與當前事務不相容, 那麼久延遲處理當前資料庫操作;否則的話,就要將鎖單位設定為期望的鎖模式, 並將資料庫操作傳遞給資料處理程式以進行實際的資料訪問。

事務管理程式會接收到操作結果的提示,而在當前事務終結之後,就可以釋放相應的鎖, 並且可以允許其他需要訪問相同資料項的事務繼續執行了。

遺憾的是,上述演算法無法正常對事務執行進行同步。這是因為,為了生成可序列化的順序, 事務的加鎖和解鎖操作同樣需要與其他操作放在一起進行協調。
示例如下,有兩個事務:
T1: BEGIN T2: BEGIN
A=A-100 A=A1.06
B=B+100 B=B1.06
COMMIT COMMIT
對於這個例子, A=1000,B=1000,T1和T2併發執行,如果T1->T2或者T2->T1的順序執行, 對於最終結果都是正確的。
T1: T2:
BEGIN
A=A-100
BEGIN
A=A1.06
B=B+100
COMMIT
B=B1.06
COMMIT
這樣也可以得到最終正確的結果。
T1: T2:
BEGIN
A=A-100
BEGIN
A=A1.06
B=B1.06
COMMIT
B=B+100
COMMIT
這時的執行順序為:
T1: T2:
BEGIN
R(A)
W(A)
BEGIN
R(A)
W(A)
R(B)
W(B)
COMMIT
R(B)
W(B)
COMMIT
這時 A+B=2014,結果錯誤。

這裡的問題是,加鎖演算法會在資料庫命令(讀或寫)執行完後就立即釋放與事務相應的鎖。在這之後,鎖單位 (比如x)就不會再被訪問了。但是,x的鎖被釋放之後, 其他鎖單位(比如y)上依然會有由該事務設定的鎖。雖然這似乎有利於提高併發性, 但是會讓事務之間相互影響,以至於喪失了隔離性和原子性。

這就是我們需要兩階段鎖的原因。

兩階段鎖的規則很簡單,即:不允許事務在釋放了它的任何一個鎖之後請求新的鎖, 直到能夠確定一個事務不再請求新的鎖,它已有鎖才可以釋放。
兩階段鎖將事務執行分為兩個階段:增長階段和收縮階段。在增長階段,事務會申請鎖並訪問資料項;在收縮階段,事務會釋放它的鎖。人們已經證明, 任何一個遵守兩階段鎖協議( Eswaran et al., 1976)的併發控制演算法生成的執行序列都是可序列化的。


concurrent3
在增長階段之後不允許繼續獲得或者更新鎖。對於資料項的訪問一旦結束,鎖管理器就會釋放鎖, 這樣其他正在等待的事務就可以鎖住該資料項並繼續執行了,系統的併發性可以得到提高。

原理看起來挺簡單,但實現起來還是有難度的,首先,怎麼確定已經從增長階段轉到收縮階段呢, 也就是需要鎖管理器確定事務已經獲得了所有需要的鎖;其次,鎖管理器也需要確定事務 不再訪問某一資料項,這樣,它就能及時釋放鎖,最後,如果事務在釋放鎖以後發生了取消操作, 那麼訪問過響應的解鎖後的資料項的其他事務也需要取消。也就是級聯取消。實現上涉及到事務中鎖的宣告週期管理,資料的訪問,回滾的級聯取消等等難點。
級聯取消示例
T1: T2:
BEGIN
A=A-100
BEGIN
A=A1.06
B=B+100
ABORT
B=B1.06
COMMIT
這個順序符合兩階段鎖協議規則,但 T1的取消也會導致T2的取消,也就是導致了級聯取消。

級聯取消問題可以使用嚴格兩階段鎖方式來解決,嚴格兩階段鎖要求所有鎖必須在事務終結時 (提交或取消)一同釋放 ,要求事務寫入的值在事務完成之前不能被其他事務讀取或覆蓋。嚴格兩階段鎖的優點是不會引起級聯取消,取消的事務可以透過恢復修改後的元組的原始值 來完成。

這裡又存在一個問題,雖然兩階段鎖演算法生成的順序都是衝突可序列化的, 但並不是所有的衝突可序列化的順序都符合兩階段鎖規則,如這個順序: {W1(X),R2(X),W3(Y),W1(Y)}。這個順序不符合兩階段鎖規則, 因為在T1需要釋放x寫鎖之後申請了一個Y的寫鎖。這裡標號1-3代表事務,W代表寫, R代表讀,X,Y代表資料項。因此,在設計加鎖演算法時需要考慮加鎖順序,以便讓順序符合2PL, 例如將順序變成T3->T1->T2。

由上邊的例子可以看出,衝突操作的執行順序和對沖突的預先檢測同樣重要。所以, 除了讀鎖(共享鎖)和寫鎖(獨佔鎖)外,還有第三種鎖模式,有序共享鎖( ordered shared lock)。有序共享鎖意味著讀-寫之間需要進行有序共享的方式進行相容。

加鎖的方式可能會造成系統的死鎖,這是因為允許對資源的獨佔訪問,兩個訪問相同資料項的事務 是可以以相反的順序對資料項進行加鎖的,這會造成其中一個事務無限等待另一個事務釋放鎖, 從而引發死鎖。

在資料庫中鎖還需要考慮資料庫的層次結構,因為資料庫都是由庫,表,元組,屬性等級別組成, 可以看做一個樹形結構,比如說在表級別加鎖,表的葉子節點(元組以及屬性)是否需要鎖定。這裡就有意向鎖( intention lock),意圖鎖允許更高階別的樹節點被鎖定在共享或獨佔鎖模式中, 而不必檢查所有的子節點。而意向鎖又分為Intention-Shared(IS)、Intention-Exclusive (IX)、Shared-Intention-Exclusive(SIX)等,主要就是在對於這個層級結構的 的不同級別使用獨佔、共享等鎖模式的不同來進行分類,這裡不再展開。意向鎖可以有效的提高資料庫的併發。意向鎖在傳統關係型資料庫中是非常有用的,甚至在各類資料庫中可以顯式的定義鎖(非SQL標準), 例如在PostgreSQL、Oracle、DB2中,顯示鎖定表:

LOCK TABLE

IN MODE。

分散式兩階段演算法

上一節所提到的兩階段鎖可以很容易的擴充套件到分散式資料庫環境中。分散式兩階段鎖可分為:
• 集中式兩階段鎖
• 分散式兩階段鎖

集中式兩階段鎖

一種方式是將鎖管理的任務交給一個單獨的節點來完成,這意味著所有的節點, 只有一個擁有鎖管理程式,其他節點中事務管理程式只是相互通訊, 而不是與各自的鎖管理器通訊 (它們不存在鎖管理器)。這種方式被稱為主節點2PL (primary site 2PL)。
依據集中式 2PL,在執行一個事務的時候,站點之間的通訊是在事務發起節點的事務管理程式 (稱為協調事務管理器coordinating TM)、中心節點上的鎖管理器™以及 其他節點中的資料處理程式(DP)之間進行的。

concurrent4
在圖中標明瞭各個訊息的順序。協調 TM作為語句入口,主鎖TM進行鎖管理,資料庫操作 在各資料節點上儲存的資料項中進行。

集中式兩節點鎖又一個公認的缺點是:主節點會很快成為整個系統的瓶頸。另外, 主節點外的節點失效或無法訪問會造成系統的嚴重故障,從而導致系統變得很不可靠。如果事務量不大時還不明顯,這一瓶頸在事務數量增多的時候會顯得越發明顯。

分散式兩階段鎖

分散式兩階段鎖要求每個站點都有一個鎖管理器,依據分散式兩階段鎖協議, 事務在執行時各節點之間的通訊如圖。


concurrent5
與中心兩階段鎖類似,但有兩個改動:
• 傳送給中心節點的鎖管理程式訊息變成傳送給所有參與節點的鎖管理程式
• 資料操作不在由協調事務管理程式傳送給資料處理程式,而是由多個排程節點進行傳遞。
這意味著協調事務管理程式不再需要等待鎖請求已批准了。這裡有個變種是,參與節點不需要 等待所有參與節點告知協調 TM操作結束由協調節點傳送釋放鎖訊息,而是, 每個資料處理程式只向它自己的鎖管理程式傳送訊息,而每個鎖管理程式負責釋放鎖, 並通知協調事務管理程式。
由於有多個排程節點,分散式兩階段鎖需要使用選舉投票的方式來決定 COMMIT和ABORT, 優點也顯而易見,沒有了單點瓶頸。

總結

本文介紹了基於鎖的併發控制,基於鎖的併發控制有效的保證了資料操作的原子性,但想要設計一套好的分散式兩階段演算法, 並進行工程化並不簡單,需要考慮很多因素。

例如以下幾個維度:
• 故障,可以容忍節點故障和通訊故障(網路分割槽、超時)等
• 阻塞,例如程式在不確定區間超時,或者通訊程式不確定導致的阻塞。
• 時間複雜度,傳統關係型資料庫可以把讀寫控制在1ms以下,分散式包含更多的通訊, 選舉投票,每一步耗時過長都會有影響。
• 訊息複雜度,假設參與者數目為n,兩階段提交每輪都有n個訊息被髮送,在沒有故障的情況下,協議就會產生3n個訊息。
所以,實現分散式兩階段提交,並持續最佳化,是一個很大的課題。

 

以上為基於加鎖的併發控制, 「分散式技術專題」是國產資料庫 hubble 團隊精心整編,專題會持續更新,歡迎大家保持關注。

 

 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70026685/viewspace-2935017/,如需轉載,請註明出處,否則將追究法律責任。

相關文章