序
此篇部落格是【眼見為實】系列的第一篇部落格,主要從理論上講了資料庫併發可能會出現的問題,解決併發問題的技術——封鎖,封鎖約定的規則——封鎖協議。然後簡單說明了資料庫事務隔離級別和封鎖協議的對應關係。後面的幾篇部落格都是通過親身實踐探究InnoDB引擎在各個隔離級別下的實現細節。
【眼見為實】自己動手實踐理解READ UNCOMMITED && SERIALIZABLE
【眼見為實】自己動手實踐理解 READ COMMITTED && MVCC
【眼見為實】自己動手實踐理解REPEATABLE READ && Next-Key Lock
資料庫併發的幾大類問題
①丟失修改(Lost Update)
兩個事務T1和T2同時讀入同一資料並修改,T2的提交的結果破壞了T1提交的結果,導致T1的修改被丟失(第二類丟失更新)。
還有一種特殊的丟失修改(第一類丟失更新),如下圖。因為這種丟失修改在【READ UNCOMMITED】隔離級別下都不會出現,所以不進行討論。
②不可重複讀(Non-Repeatable Read)
事務T1讀取資料後,事務T2執行更新操作,使事務T1無法再現前一次讀取結果。 具體包括三種情況: (1)事務T1讀取某一資料後,事務T2對其做了修改,當事務T1再次讀取該資料時,得到與前一次不同的值。
(2)事務T1按照一定條件讀取了某些資料記錄後,事務T2刪掉了其中部分記錄,當T1再次按相同條件查詢資料時,發現某些記錄消失了。 (3)事務T1按照一定條件讀取了某些資料記錄後,事務T2插入了一些記錄,當T1再次按相同條件查詢資料時,發現多了一些記錄。
##③幻讀(Phantom Read)
幻讀其實是不可重複讀的一種特殊情況。不可重複讀(2)和(3)也稱為幻讀現象。不可重複讀是對資料的修改更新產生的;而幻讀是插入或刪除資料產生的。
④讀髒資料(Dirty Read)
事務T1修改某一資料,並將其寫回磁碟,事務T2讀取同一資料後,T1因為某些原因回滾,這時T1修改過的資料恢復原值,T2讀取到的資料就與資料庫中的資料不一致,則T2讀取到資料就為“髒資料“,即不正確的資料。
併發控制的主要技術是封鎖
基本封鎖型別: ①排它鎖(Exclusive Locks,簡稱X鎖) 排它鎖又稱為寫鎖。若事務T對資料物件A加上X鎖,則只允許T修改和讀取A,其他任何事務都不能再對A加任何型別的鎖,直到T釋放A上的鎖。這就保證了其他事務在T釋放A上的鎖之前都不能再讀取和修改A。 ②共享鎖(Share Locks,簡稱S鎖) 共享鎖又稱為讀鎖。若事務T對資料物件A加上S鎖,則事務T可以讀取A但不能修改A。其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這就保證了其他事務可以讀取A,但是在T釋放A上的S鎖之前不能對A做任何修改。
排它鎖與共享鎖的相容矩陣
封鎖協議
在運用X鎖和S鎖這兩種基本封鎖,對資料物件加鎖時,還需要約定一些規則。例如何時申請X鎖和S鎖,持鎖時間,何時釋放等。這些規格稱為封鎖協議。
一級封鎖協議
一級封鎖協議:事務T在修改資料A之前必須對其加X鎖,直到事務結束才釋放。事務結束包括正常結束(Commit)和非正常結束(RollBack)。 一級封鎖協議可防止丟失修改。 使用一級封鎖協議解決了圖1中的覆蓋丟失問題。事務T1在讀A進行修改之前先對A加X鎖,當T2再請求對A加X鎖時被拒絕,T2只能等待T1釋放A上的鎖後T2獲得A上的X鎖,這時它讀取的A已經是T1修改後的15,再按照此值進行計算,將結果值A=14寫入磁碟。這樣就避免了丟失T1的更新。
二級封鎖協議
二級封鎖協議:一級封鎖協議加上事務T在讀取資料A之前必須先對其加S鎖,讀完後即可釋放S鎖。 二級封鎖協議除防止了丟失修改,還進一步防止了讀“髒”資料。 使用二級封鎖協議解決了圖2中的髒讀問題。事務T1在讀C進行修改之前先對C加X鎖,修改其值後寫回磁碟。這時T2請求在C上加S鎖,因為T1在C上已經加了X鎖,所以T2只能等待。T1因為某種原因被撤銷,C恢復原值100。T1釋放C上的X鎖後T2獲得C上的S鎖,讀C=100。這樣就避免了讀“髒”資料。
三級封鎖協議
三級封鎖協議:一級封鎖協議加上事務T在讀取資料A之前必須先對其加S鎖,直到事務結束才釋放。 三級封鎖協議除防止了丟失修改和讀“髒”資料,還進一步防止了不可重複讀。 使用三級封鎖協議解決了圖3中的不可重複讀問題。事務T1在讀取資料A和資料B之前對其加S鎖,其他事務只能再對A、B加S鎖,不能加X鎖,這樣其他事務只能讀取A、B,而不能更改A、B。這時T2請求在B上加X鎖,因為T1已經在B上加了S鎖,所以T2只能等待。T1為了驗算結果再次讀取A、B的值,因為其他事務無法修改A、B的值,所以結果仍然為150,即可重複讀。此時T1釋放A、B上的S鎖,T2才獲得B上的X鎖。這樣就避免了不可重複讀。
活鎖和死鎖
封鎖可能會引起活鎖活死鎖。
活鎖
如果事務T1封鎖了資料R,事務T2又請求封鎖資料R,於是T2等待。事務T3也請求封鎖R,當事務T1釋放了資料R上的封鎖之後系統首先批准了事務T3的封鎖請求,T2仍然等待。然後T4又申請封鎖R,當T3釋放了R的封鎖之後系統又批准了T4的封鎖請求。T2有可能一直等待下去,這就是活鎖。
避免活鎖的方法就是先來先服務的策略。當多個事務請求對同一資料物件封鎖時,封鎖子系統按照請求的先後對事務排隊。資料物件上的鎖一旦釋放就批准申請佇列中的第一個事務獲得鎖。
##死鎖
如果事務T1封鎖了資料R1,事務T2封鎖了資料R2,然後T1又請求封鎖資料R2,因為T2已經封鎖了資料R2,於是T1等待T2釋放R2上的鎖。接著T2又申請封鎖R1,因為因為T1已經封鎖了資料R1,T2也只能等待T1釋放R1上的鎖。這樣就出現了T1在等待T2,T2也在等待T1的局面,T1和T2兩個事務永遠不能結束,形成死鎖。
死鎖的預防:
①一次封鎖法
一次封鎖法要求事務必須一次將所有要使用的資料全部加鎖,否則不能繼續執行。例如上圖中的事務T1將資料R1和R2一次加鎖,T1就能執行下去,而T2等待。T1執行完成之後釋放R1,R2上的鎖,T2繼續執行。這樣就不會產生死鎖。
一次封鎖法雖然能防止死鎖的發生,但是缺點卻很明顯。一次性將以後要用到的資料加鎖,勢必擴大了封鎖的範圍 ,從而降低了系統的併發度。
②順序封鎖法
順序封鎖法是預先對資料物件規定一個封鎖順序,所有的事務都按照這個順序實行封鎖。
順序封鎖法雖然可以有效避免死鎖,但是問題也很明顯。第一,資料庫系統封鎖的資料物件極多,並且隨著資料的插入、刪除等操作不斷變化,要維護這樣的資源的封鎖順序非常困難,成本很高。第二,事務的封鎖請求可以隨著事務的執行動態的確定,因此很難按照規定的順序實行封鎖。
可見,預防死鎖的產生並不是很適合資料庫的特點,所以在解決死鎖的問題上普遍採用的是診斷並且解除死鎖。
死鎖的診斷與解除:
①超時法
如果一個事務的等待時間超過了預設的時間,就認為是產生了死鎖。
②等待圖法
一旦檢測到系統中存在死鎖就要設法解除。通常的解決方法是選擇一個處理死鎖代價最小的事務,將其撤銷,釋放此事務持有的所有的鎖,恢復其所執行的資料修改操作,使得其他事務得以執行下去。
兩段鎖協議
所謂的二段鎖協議是指所有事務必須分兩個階段對資料進行加鎖和解鎖操作。
-
在對任何資料進行讀、寫操作之前,首先要申請並獲得該資料的封鎖。
-
在釋放一個封鎖之後,事務不在申請和獲得其他封鎖。
也就是說事務分為兩個階段。第一個階段是獲得封鎖,也稱為擴充套件階段。在這個階段,事務可以申請獲得任何資料項任何型別的鎖,但是不能釋放任何鎖。第二階段是釋放封鎖,也稱為收縮階段。在這個階段,事務可以釋放任何資料項上任何型別的封鎖,但是不能再申請任何鎖。
事務遵守兩段鎖協議是可序列化排程的充分條件,而不是必要條件。也就是說遵守兩段鎖協議一定是可序列化排程的,而可序列化排程的不一定是遵守兩段鎖協議的。
左側T1、T2遵循兩段鎖協議,右側T1、T2並不遵循兩段鎖協議兩段鎖協議和一次封鎖法的異同
一次封鎖法要求事務必須將要使用的資料全部加鎖,否則不能繼續執行。因此一次封鎖法遵守兩段鎖協議。
但是兩段鎖協議並不要求事務將要使用的資料一次全部加鎖,因此兩段鎖協議可能發生死鎖。如圖:
資料庫隔離級別
封鎖協議和隔離級別並不是嚴格對應的。