CMU-15445 LAB3:事務隔離,two-phase locking,鎖管理器

概述

本lab將實現一個鎖管理器,事務經過鎖管理器獲取鎖,事務管理器根據狀況決定是否授予鎖,或是阻塞等待其它事務釋放該鎖。html

背景

事務屬性

衆所周知,事務具備以下屬性:git

  1. 原子性:事務要麼執行完成,要麼就沒有執行。
  2. 一致性:事務執行完畢後,不會出現不一致的狀況。
  3. 隔離性:多個事務併發執行不會相互影響。
  4. 持久性:事務執行成功後,因此狀態將被持久化。

一些定義

將對數據對象Q的操做進行抽象,read(Q):取數據對象Q,write(Q)寫數據對象Q。github

schedule

考慮事務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 serializable3d

好比下面這個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的。

two-phase locking

不對加解鎖進行限制

前面提到多個事務併發執行的時候,可能出現數據不一致得狀況。一個很顯然的想法是加鎖來進行併發控制。
可使用共享鎖(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

這時引入了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);

如今不管如何都不會出現數據不一致的狀況了。

two-phase locking正確性證實

課本的課後題15.1也要求咱們證實two-phase locking(如下稱2PL rule)的正確性。我看了下解答,用的是反正法。我還看到一個用概括法證的,比較有趣。
前提:

  1. 假設T1, T2, ... Tn,n個事務遵循two-phase locking協議。
  2. Sn是T1, T2, ... Tn併發執行的一個schdule。

目標:
證實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中第一個解鎖的操做:
lab3_1_proof.PNG

能夠證實,咱們能夠將事務i全部ri(•) and wi(•)操做移到Sn的最前面,而不會引發conflict。
證實以下:
令Wi(Y)是事務i的任意操做,Wj(Y)是事務j的一個操做,而且和Wi(Y)conflict。等價於證實不會出現以下這種狀況:
lab3_2_proof.PNG

假設出現了這種狀況,那麼必然有以下加解鎖順序:
lab3_3_proof.PNG

又由於全部事務都遵照2PL rule,因此必然有以下加解鎖順序:
lab3_4_proof.PNG

衝突出現了,Ui(•)應該是Sn中第一個解鎖操做,可是如今倒是Uj(Y)。因此假設不成立,因此結論:"咱們能夠將事務i全部ri(•) and wi(•)操做移到Sn的最前面,而不會引發conflict"成立。

咱們將事務i的全部操做移到schedule最前面,
lab3_5_proof.PNG

又由於Sn-1是conflict serializable的因此Sn是conflict serializable的。

證實完畢

two-phase locking不能保證不會死鎖

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上的鎖,形成死鎖。

死鎖處理

有兩類基本思路:

  1. 死鎖預防,這類方法在死鎖出現前就能發現可能致使死鎖的操做。
  2. 死鎖檢測,這類方法按期執行死鎖檢測算法,看是否發生死鎖,若是發生了,執行死鎖恢復算法。

這裏介紹wait-die這種死鎖預防機制,該機制描述以下:
事務Ti請求某個數據項,該數據項已經被事務Tj獲取了鎖,Ti容許等待當且僅當Ti的時間戳小於Tj,不然Ti將被roll back。

wait-die正確性證實

爲何該機制能保證,不會出現死鎖的狀況呢?
若是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對外提供四個接口函數:

  1. LockShared(Transaction *txn, const RID &rid):事務txn但願獲取數據對象rid的讀鎖,對應上述lock-S()。
  2. LockExclusive(Transaction *txn, const RID &rid):事務txn但願獲取數據對象rid的寫鎖,對應上述的lock-X()。
  3. LockUpgrade(Transaction *txn, const RID &rid):將寫鎖升級爲讀鎖。
  4. Unlock(Transaction *txn, const RID &rid):對應上述unloxk()。

能夠用以下數據結構來實現:
lab3_6_lock_manager.PNG

每一個數據項對應一個鏈表,該鏈表記錄請求隊列。
當一個請求到來時,若是請求的數據項當前沒有任何事務訪問,那麼建立一個空隊列,將當前請求直接放入其中,受權經過。若是不是第一個請求,那麼將當前事務加入隊列,只有當前請求以前的請求和當前請求兼容,才受權,不然等待。

在哪裏調用LockManager呢?
page/table_page.cpp中的TablePage類用於插入,刪除,更新,查找表記錄。在執行插入,刪除,查找前都會獲取相應的鎖,確保多個事務同時操做相同數據項是安全的。

LockManager的具體代碼能夠參考個人手實現:https://github.com/gatsbyd/cmu_15445_2018

參考資料:

  1. http://www.mathcs.emory.edu/~cheung/Courses/554/Syllabus/7-serializability/2PL.html
  2. 《Database System concepts》 chapter 14, 15
相關文章
相關標籤/搜索