本lab將實現一個鎖管理器,事務經過鎖管理器獲取鎖,事務管理器根據狀況決定是否授予鎖,或是阻塞等待其它事務釋放該鎖。html
衆所周知,事務具備以下屬性:git
將對數據對象Q的操做進行抽象,read(Q):取數據對象Q,write(Q)寫數據對象Q。github
考慮事務T1,T1從帳戶A向帳戶B轉移50。算法
T1: read(A); A := A - 50; write(A); read(B); B := B + 50; write(B).
事務T2將帳戶A的10%轉移到帳戶B。安全
T2: read(A); temp := A * 0.1; A := A - temp; write(A); read(B); B := B + temp; write(B).
假設帳戶A、B初始值分別爲1000和2000。
咱們將事務執行的序列稱爲schedule。以下面這個schedule,T1先執行完,而後執行T2,最終的結果是具備一致性的。咱們稱這種schedule爲serializable schedule。數據結構
T1 T2 read(A); A := A - 50; write(A); read(B); B := B + 50; write(B). read(A); temp := A * 0.1; A := A - temp; write(A); read(B); B := B + temp; write(B).
可是看下面這個shedule:併發
T1 T2 read(A); A := A - 50; read(A); temp := A * 0.1; A := A - temp; write(A); read(B); write(A); read(B); B := B + 50; write(B). read(B); B := B + temp; write(B).
執行完帳戶A和B分別爲950和2100。顯然這個shecule不是serializable schedule。函數
考慮連續的兩條指令I和J,若是I和J操做不一樣的數據項那麼,這兩個指令能夠交換順序,不會影響schedule的執行結果。若是I和J操做相同的數據項,那麼只有當I和J都是read(Q)時纔不會影響schedule的結果。若是兩條連續的指令,操做相同的數據項,其中至少一個指令是write,那麼I和J是conflict的。ui
若是schedule S連續的條指令I和J不conflict,咱們能夠交換它們執行的順序,從而產生一個新的schedlue S',咱們稱S和S'conflict equivalent。若是S通過一系列conflict equivalent變換,和某個serializable schedule等價,那麼咱們稱S是conflict serializable。3d
好比下面這個schedule S:
T1 T2 read(A); write(A); read(A); write(A); read(B); write(B); read(B); write(B);
通過屢次conflict equivalent變換,生成新的schedule S',S'是serializable schedule。
T1 T2 read(A); write(A); read(B); write(B); read(A); write(A); read(B); write(B);
因此S是conflict serializable的。
前面提到多個事務併發執行的時候,可能出現數據不一致得狀況。一個很顯然的想法是加鎖來進行併發控制。
可使用共享鎖(lock-S),排他鎖(lock-X)。
問題來了。
在何時加鎖?何時釋放鎖?
考慮下面這種加解鎖順序:
事務一從帳戶B向帳戶A轉移50。
T1: lock-X(B); read(B); B := B - 50; write(B); unlock(B); lock-X(A); read(A); A := A + 50; write(A); unlock(A).
事務二展現帳戶A和B的總和。
T2: lock-S(A); read(A); unlock(A); lock-S(B); read(B); unlock(B); display(A+B).
可能出現這樣一種schedule:
T1 T2 lock-X(B); read(B); B := B - 50; write(B); unlock(B); lock-S(A); read(A); unlock(A); lock-S(B); read(B); unlock(B); display(A+B). lock-X(A); read(A); A := A + 50; write(A); unlock(A).
假設初始時A和B分別是100和200,執行後事務二顯示A+B爲250,顯然出現了數據不一致。
咱們已經加了鎖,爲何還會出現數據不一致?
問題出在T1過早unlock(B)。
這時引入了two-phase locking協議,該協議限制了加解鎖的順序。
該協議將事務分紅兩個階段,
Growing phase:事務能夠獲取鎖,可是不能釋聽任何鎖。
Shringking phase:事務能夠釋放鎖,可是不能獲取鎖。
最開始事務處於Growing phase,能夠隨意獲取鎖,一旦事務釋放了鎖,該事務進入Shringking phase,以後就不能再獲取鎖。
按照two-phase locking協議重寫以前的轉帳事務:
事務一從帳戶B向帳戶A轉移50。
T1: lock-X(B); read(B); B := B - 50; write(B); lock-X(A); read(A); A := A + 50; write(A); unlock(B); unlock(A).
事務二展現帳戶A和B的總和。
T2: lock-S(A); read(A); lock-S(B); read(B); display(A+B). unlock(A); unlock(B);
如今不管如何都不會出現數據不一致的狀況了。
課本的課後題15.1也要求咱們證實two-phase locking(如下稱2PL rule)的正確性。我看了下解答,用的是反正法。我還看到一個用概括法證的,比較有趣。
前提:
目標:
證實Sn是conflict serializable的schedule。
證實開始:
起始步驟,n = 1的狀況:
T1遵照2PL rule。
S1這個schedule只包含T1。
顯然S1是conflict serializable的schedule。
迭代步驟:
迭代假設:假設Sn-1是T1, T2, ... Tn−1造成的一個schedule,而且Sn-1是conflict serializable的schedule。咱們須要證實Sn-1是conflict serializable的schedule,Sn也是conflict serializable的schedule。
假設Ui(•)是事務i的解鎖操做,而且是schedule Sn中第一個解鎖的操做:
能夠證實,咱們能夠將事務i全部ri(•) and wi(•)操做移到Sn的最前面,而不會引發conflict。
證實以下:
令Wi(Y)是事務i的任意操做,Wj(Y)是事務j的一個操做,而且和Wi(Y)conflict。等價於證實不會出現以下這種狀況:
假設出現了這種狀況,那麼必然有以下加解鎖順序:
又由於全部事務都遵照2PL rule,因此必然有以下加解鎖順序:
衝突出現了,Ui(•)應該是Sn中第一個解鎖操做,可是如今倒是Uj(Y)。因此假設不成立,因此結論:"咱們能夠將事務i全部ri(•) and wi(•)操做移到Sn的最前面,而不會引發conflict"成立。
咱們將事務i的全部操做移到schedule最前面,
又由於Sn-1是conflict serializable的因此Sn是conflict serializable的。
證實完畢
two-phase locking能夠保證conflict serializable,但可能會出現死鎖的狀況。
考慮這個schedule片斷:
T1 T2 lock-X(B); read(B); B := B - 50; write(B); lock-S(A); read(A); lock-S(B); lock-X(A);
T1和T2都遵循2PL rule,可是T2等待T1釋放B上的鎖,T1等待T2釋放A上的鎖,形成死鎖。
有兩類基本思路:
這裏介紹wait-die這種死鎖預防機制,該機制描述以下:
事務Ti請求某個數據項,該數據項已經被事務Tj獲取了鎖,Ti容許等待當且僅當Ti的時間戳小於Tj,不然Ti將被roll back。
爲何該機制能保證,不會出現死鎖的狀況呢?
若是Ti等待Tj釋放鎖,咱們記Ti->Tj。那麼系統中全部的事務將組成一個稱做wait-for graph的有向圖。容易證實:wait-for graph出現環和系統將出現死鎖等價。
wait-die這種機制就能防止出現wait-for graph出現環。爲何?由於wait-die機制只容許時間戳小的等待時間戳大的事務,也就是說在wait-for graph中任意一條邊Ti->Tj,Ti的時間戳都小於Tj,顯然不可能出現環。因此不會出現環,也就不可能出現死鎖。
事務管理器LockManager對外提供四個接口函數:
能夠用以下數據結構來實現:
每一個數據項對應一個鏈表,該鏈表記錄請求隊列。
當一個請求到來時,若是請求的數據項當前沒有任何事務訪問,那麼建立一個空隊列,將當前請求直接放入其中,受權經過。若是不是第一個請求,那麼將當前事務加入隊列,只有當前請求以前的請求和當前請求兼容,才受權,不然等待。
在哪裏調用LockManager呢?
page/table_page.cpp中的TablePage類用於插入,刪除,更新,查找表記錄。在執行插入,刪除,查找前都會獲取相應的鎖,確保多個事務同時操做相同數據項是安全的。
LockManager的具體代碼能夠參考個人手實現:https://github.com/gatsbyd/cmu_15445_2018
參考資料: