(十六、十七)數據庫並發控制(上)
1. 簡介和引入知識
1. 事物
1. 事物的概念
事物這個概念在數據庫中可謂是最為常見。它是指一些列操作序列(一個或一個以上)當一個事務被提交給了DBMS(數據庫管理系統),則DBMS需要確保該事務中的所有操作都成功完成且其結果被永久保存在數據庫中,如果事務中有的操作沒有成功完成,則事務中的所有操作都需要被回滾,回到事務執行前的狀態(要么全執行,要么全都不執行);同時,該事務對數據庫或者其他事務的執行無影響,所有的事務都好像在獨立的運行。從事物的概念出發。就可以引出事物的四大特性。
一些形式化的定義為了方便后面的描述
A,B,C來表示數據對象
R(A),W(B) 來表示對於數據對象的讀寫操作
2. 事物的四大特性
這里感覺cmu ppt里面對於四種特性一句話的概括特別好。
- 原子性(Atomicity): all or nothing
- 一致性(Consistency): it looks correct to me
- 隔離性(Isolation): as if alone
- 持久性(Durability): survive failures
3. 確保原子性的機制
首先考慮這樣一個問題。加入我把當前賬戶的$100取出轉賬給andy。但是在我們取出它之后,轉賬給andy之前這個事物突然終止了。或者停電了。這樣如果dbms什么都不做,就有100 ¥ 蒸發掉。那么如何解決這一問題。
1. Logging(日志)
dmbs的日志會記錄所有的行為。這樣就可以當事物被abort的時候撤銷這個事物已經執行了的無效行為。幾乎所有的DBMS
都是用了這種方法
2. Shadow Paging
在這種機制下DBMS復制所有的page。當事物對這些page進行改變的時候。會改變這些page的副本。只有當這個事物成功commit之后。這些被改變的副本就會對其他用戶可見。
2. 兩種常見的並發協議和例子
並發協議指的是。dbms如何控制多個事物的交錯執行。
兩種最常見的協議分別是
- 悲觀協議: 從一開始就不要讓問題出現。
- 樂觀協議: 假設沖突非常少。只有當發生的時候才會解決它
下面來看一些並發交錯執行的例子
1. 順序執行example
Assume at first A and B each have $1000.
左邊T1先執行T2后執行整個執行過程大概如下
T1:
A = A - 100 = 900
B = B + 100 = 1100
T2 :
A = A * 1.06 = 954
B = B * 1.06 = 1166
右邊則T2先執行。T1后執行。執行的結果和左邊是完全一致的
2. INTERLEAVING EXAMPLE (GOOD)
可以發現對於左邊。雖然在兩個事物之間有交叉。但是最后的結果是一樣的。我們說左邊這個調度是可序列化的。
3. INTERLEAVING EXAMPLE (BAD)
對於上面的操作。他最后的結果和序列化的結果不一樣。則這個調度就是不正確的
3. 幾種沖突
發生沖突的操作主要分為下面三大類
- Read-Write Conflicts (R-W)
- Write-Read Conflicts (W-R)
- Write-Write Conflicts (W-W)
1. READ-WRITE CONFLICTS
讀寫沖突造成的問題就是不可重復讀的問題
事物T1先開始執行。首先讀出來A的值發現位10。接下來執行權交給了事物T2運行。T2對A進行了寫操作。但是對於事物T1而言,它會認為它沒有對A進行了修改操作。因此當事物T2提交之后,執行權回到T1的時候,T1再讀取A發現讀出來的為19。這里就存在這錯誤。
2. WRITE-READ CONFLICTS
讀未提交的數據(臟讀)
這里發生的問題就是。事物T1先修改了A的值,但是后面這個事物被abort了。但是T2以為這個值已經被修改了。所以它直接讀了T1修改完之后讀值進行了操作。
3. WRITE-WRITE CONFLICTS
這里對於T1而言它不知道A被重新寫了兩次。
2. CONFLICT SERIALIZABILITY INTUITION
首先通過兩個例子看一下有沖突的可以序列化的調度。和有沖突不能序列化的調度
沖突的幾種情況在上面已經介紹過了
可以發現左邊和右邊並不等價。
那如何判斷一個調度是否是沖突可序列化的那。最常用的算法就是依賴圖算法(lab4中也有用到這個)
點:事物來表示點
邊:如果事物Ti的一個操作Oi與事物Tj的一個操作Oj發生沖突。並且操作Oi發生在Oj之前。那么存在一條從事物Ti指向事物Tj的邊
如果出現環。則無法沖突可序列化
1. 依賴圖算法舉例
1.1 例子一。😈
1.2 例子2 🌟
- 首先有三個事物。先在圖中畫出三個點
- 找到不同事物之間的沖突關系。然后得到邊
2. VIEW SERIALIZABILITY算法
除了依賴圖之外。還有一個算法來判斷是否可以沖突序列化
這個算法讓我們站到一個更高的層次來判斷是否可以序列化。如果兩個調度S和S‘滿足下面的條件。則我們說這兩個調度等價
對於上面這個例子。如果是基於依賴圖算法的話。那么它就是一個無法沖突可序列化的調度。但是根據VIEW SERIALIZABILITY算法
。它可以轉換成一個可序列化的算法
對於這兩個調度。我們站到更高的層次上來看。對於讀操作。都是讀到最剛開始A的值。對於寫操作。寫的順序雖然交換了。但是並不影響結果。
3. 兩段鎖協議
最經典。也是實驗要用到的並發協議。
首先為什么叫兩段鎖協議那。肯定是因為它有兩個階段吧(:。
階段1:Growing
這一階段每個事物需要從DBMS的鎖管理處獲得鎖
階段2:Shrinking
這一階段事物只允許釋放之前獲得的鎖。而不允許加上新的鎖
在看一個例子之前。先來看一下這個協議中的鎖的類型。
兩大類的鎖分別為排他鎖和共享鎖
3.1二階段鎖協議的例子
從這個例子來看二階段鎖似乎可以完美的運行。事物T1先開始執行。由於這一事物的操作中有寫操作。所以事物T1對A加上排他鎖。這樣當事物T2開始的時候。由於事物T2也想寫A。所以事物T2也去嘗試加上排他鎖。但是由於A此時有一個排他鎖。(事物T1持有的。此時還沒有釋放)所以事物T2這個時候就需要wait
。等到事物T釋放對於A的鎖之后。事物T2才能獲得對於A的加鎖權。隨后事物T2對A執行寫操作。
但是二階段鎖有他的缺點
比如對於上面這種情況。事物T1先修改了A。但是這個事物后面ABORT
掉了。但是T2並不知道,所以T2這里還是會讀出A的值。然后在進行修改。這樣就可能產生連鎖錯誤。也就是上面提到的。臟讀。所以為了解決這種問題。DBMS會因為T1的abort也把T2 abort掉。
3.2 強限制的2PL
看下面這個例子。有兩個事物。分別如下
先看沒有2PL的情況
總感覺這里應該是1900。不過肯定有問題就是了。下面看引入2PL。如何解決這一問題
這里符合2PL。加鎖和解鎖兩個階段完全分開。而且對於有寫操作的加X鎖。對於只需要讀操作的加S鎖
嚴格的2PL協議是說如果這個事物。如果一個value要被一個事物寫的話。那么在這個事物完成之前。其他的事物都不允許讀或者重寫它
這樣就可以避免。臟讀的出現。
3.4 2PL可能會導致死鎖
死鎖現象是由於兩個事物循環等待鎖。導致的
如何探測死鎖在上面的依賴圖算法有說過
1. 死鎖處理 ---> 犧牲者 + 回滾
根據一定的原則選擇一個犧牲者。(可以是年齡最小、最大、或者是最近最少使用等等原則)。然后決定回滾多少關於這個事物的改變。
2. 死鎖預防
2.1. 當一個事物嘗試去獲得一個由其他事物持有的鎖時。DBMS會選擇一個kill掉來防止死鎖。
有兩種不同的策略來實現這一機制
這是一種基於搶占的機制
這里的優先級就是它開始的時間戳。
[Attention]: 這里的事物如果回滾了。它會以它之前的時間戳重啟
用下面這個例子來解釋一下上面的策略。
- T1 比T2有更高的優先級。T1這里需要等待T2對於A的鎖釋放。如果按照
wait-Die
而言。優先級高的要等待優先級低的釋放。所以T1就會等待。但是如果按照wait-wait
而言t2就會被aborts掉。
2.2 Lock timeouts
這種機制下。一個事物等待鎖的時間是有限制的。如果超時的話它就會回滾和重啟。
這種機制非常容易實現。但是問題就是如何界定這個超時時間
3.5 意向鎖
1. 為什么需要意向鎖
下面來看一下意向鎖的作用:參考自https://www.zhihu.com/question/51513268/answer/834445344
事務A鎖住了表中的一行,讓這一行只能讀(數據庫自動給該表增加意向共享鎖),不能寫。之后,事務B申請整個表的寫鎖。那么事物B會怎么做那。我們來看沒有意向鎖的
如果沒有意向鎖則是這樣的:
step1:判斷表是否已被其他事務用表鎖鎖表
step2:判斷表中的每一行是否已被行鎖鎖住。
如果沒有意向鎖的話,則需要遍歷所有整個表判斷是否有行鎖的存在,以免發生沖突
那么如果增加了意向鎖之后那
step1:判斷表是否已被其他事務用表鎖鎖表
step2:發現表上有意向共享鎖,說明表中有些行被共享行鎖鎖住了,因此,事務B申請表的寫鎖會被阻塞。
如果有了意向鎖,只需要判斷該意向鎖與即將添加的表級鎖是否兼容即可。因為意向鎖的存在代表了,有行級鎖的存在或者即將有行級鎖的存在。因而無需遍歷整個表,即可獲取結果。
2. 帶有意向鎖的加鎖協議
首先我們要知道意向鎖之間是互相兼容的
意向共享鎖(IS) | 意向排他鎖(IX) | |
---|---|---|
意向共享鎖(IS) | 兼容 | 兼容 |
意向排他鎖(IX) | 兼容 | 兼容 |
但是它是和普通的排他/共享鎖互斥
意向共享鎖(IS) | 意向排他鎖(IX) | |
---|---|---|
共享鎖(S) | 兼容 | 互斥 |
排他鎖(X) | 互斥 | 互斥 |
SIX:
表示這一子樹的根結點是被共享鎖鎖住。而在低層次的結點(比如葉子結點)會加排他鎖。
這里附上帶有意向鎖的兼容矩陣
下面看一個有三個事物的例子
假設按照順序。T1有更高的優先級
這里由於T1要掃描整個表同時更新一些tuple。所以這里我們要對這個表加SIX
對於事物T2。只讀取一個tuple。所以對整個表+ IS鎖。同時對自己讀區的tuple。加S鎖,當然如果這個tuple在之前被T1加了X鎖的話則就必須要等待了⌛️
隨后對於事物T3。要掃描整個R。就要先等待了。因為整個表還有SIX鎖。就表示有某些tuple。處於X鎖
這里要等待對於整個表的SIX意向鎖釋放之后。就可以掃描整個表了。
簡而言之意向鎖的作用。就是可以實現表及鎖和行級鎖共存。
- 如果
表X鎖
被占有,則其他事務嘗試獲得IS
、IX
均會阻塞,也就不能繼續獲取行X鎖
或行S鎖
了. - 如果
表S鎖
被占有,則其他事務可以獲得IS
, 而獲得IX
會阻塞. 從而阻止其他事務獲得行X鎖
4. Lock Manager實現補充
1. Lock Table
這一部分是書上的內容。就是講解鎖管理機制是如何管理鎖表的。
是利用上圖的鏈式哈希表來實現對於鎖得管理。可以看到對於元素I23
事物T1、T8正在持有鎖。而事物T2正在等待鎖。比如T1、T8正在讀該元素因此它們兩個都加了S鎖。但是T2想要修改這個元素也就是T2想要加X鎖。因此這里T2就必須等待T1、T8。S鎖的釋放。這里也是我們lab4實現整個鎖管理的主要結構
2. 插入、刪除操作實現
之前我們的注意力都在讀操作和寫操作上。下面我們把目光聚集到插入和刪除等操作上。
我們用\(I_i\) 表示事物Ti的操作。用\(I_j\) 表示事物Tj的操作。Let \(I_i = delete(Q)\)。我們考慮不同種類的\(I_j\)
對於插入操作。我們無法對一個不存在的元素。進行讀和寫。在2PL協議中。插入操作就可以看成寫操作。也就是說可以獲得X鎖。如果Ti要執行Insert(Q)
操作。那么Ti會給新加入的元素Q一個X鎖。