事務
一,什麼是事務?
構成單一邏輯工作單元的操作集合。是訪問並且可能更新各種數據項的一個程序執行單元。事務用形如begin transaction 和 end transaction 界定,並且由其之間的全部操作組成。
二,事務的四大特性(ACID)
用一個簡單事務模型來說明事務的各個特性。
設事務T爲用戶A給B轉50$。
T |
read(A) |
A=A-50 |
write(A) |
read(B) |
B=B+50 |
write(B) |
整個過程爲A賬戶先減少50$,然後B賬戶增加 50$。整個過程爲一個事務。
①原子性(atomicity)
整個過程看成一個事務。如果 在A賬戶錢減少之後,也就是write(A)完成後,系統出現故障。那麼整個系統將會丟失50$,這顯然是不行的。所以我們說事務的原子性就是一個事務要麼不開始,要麼開始就保證一定完成。如何在即使系統故障的情況下保證事務的原子性呢?可以在事務開始前將整個流程記錄在一個叫做日子文件的磁盤上,如果系統發生故障,我們可以通過日子文件將事務還原到哦最初的狀態。
②一致性(consistency)
前面說到,當系統出現 故障的時候,整個系統將會丟失50$,即事務開始前A+B 不等於 事務執行後(發生故障)的A+B,我們說事務是不一致的,顯然不一致是我們不想看到的。然而,一個事務在執行過程中必然發生不一致性(如在A賬戶減少50$的時候此時B賬戶還未增加50$)。在這裏一致性爲事務T執行前後系統A B 的和不變。
③隔離性(isolation)
當多個是事務併發執行時候,事務之間可能會交叉執行,可能會導致不一致的狀態。如事務T在執行write(A)後,另外一個併發是事務也在讀取A B和的值,並求和,顯然的結果是不一致的,因爲此時B賬戶還未增加餘額。進一步如果該事務將A+B的值存入系統中,即使T事務後面完成,最後系統的數據肯定的不一致的。隔離性保證多個事務併發執行是不會產生不一致的結果。一種非常長簡單的方法是,讓事務串行的執行即事務一個接一個的執行,然而這要做效率是相當低的因爲一個事務執行前必須等待前一個事務執行完後。
④持久性(durability)
持久性就是一旦事務已經完成,事務的操作必定已經到數據庫(磁盤)裏面去了,即使之後系統發生故障,數據庫信息也不會丟失了。顯然磁盤上的信息不想內存中,即使斷電也不受到任何影響。
三,事務各類狀態
活動狀態:事務正在執行操作的狀態。
部分提交狀態:事務已經執行完最後一條語句。
失敗狀態:發現正常的執行不能再繼續下去了。
中止狀態:事務已經回滾並且數據庫恢復爲原來事務開始之前的狀態。
提交狀態:事務成功完成後提交(committed)。
事務最初開始執行,執行到某條語句發現執行不下去了或者所有語句都執行完了系統 斷電了,(系統開始恢復)於是進入中止。否則事務正常執行完成並且提交。
1,關於事務中止以及回滾
事務中止回滾是通過存儲再磁盤上的日誌文件進行維護的,一旦出於某些原因導致事務中止,則系統通過日誌上相關信息,使得系統回到事務開始之前的狀態。日誌(log)中存儲了相關事務的標識符以及被更新數據項的標識符,以及該項的舊值和新值(修改後)。只有數據被寫入日誌後,事務纔開始執行,這也保證了事務原子性。
2,補償事務
一旦事務成功的提交後,事務是不可能通過中止回滾到最初事務開始時候的樣子的,只有通過另外開啓一個事務,來“抵消“以前的事務,另外開啓的事務爲補償事務。如:將A賬戶金額減去50$的事務T成功提交,想回到T開始前,只能通過另外開啓一個事務T'將A賬戶金額減去50$。
3,可見外部寫
有些事務可能需要將數據輸出到屏幕,或者其他外部可見的設備上,並且在事務執行過程中,一旦事務輸出到外部後,是不可一撤消的,即便是事務並沒有提交,但系統出現故障。解決這類問題辦法是先將要輸出的數據寫入非易失性存儲器上,如磁盤。然後在事務成功提交後再將數據輸出到外部,即便是數據輸出一半後系統出現故障導致數據不能再輸出,我們可以在系統恢復後,再次確認先前事務是否提交,然後從磁盤上將數據向外部輸出。避免了數據輸出一半而事務停止而無法回滾的情況。
四,事務的隔離性
先前提到,事務的隔離性可以通過,事務串行的執行即一個接一個事務的執行來實現。但其會降低系統效率,比如,有些事務涉及一些非常耗時的I/O操作,使得後面的事務不得不等到其完成後方可開始,這使得CPU資源的極大浪費。所以我們需要研究如何使得事務併發執行,但又不會數據庫的結果的方法即使併發控制機制,來提高系統資源利用率以及減少事務等待時間。
調度
事務是由一系列指令組成的,我們稱這些指令的按照時間執行的順序稱爲調度,一組事務的調度必須包含該組事務的全部指令
可以看到事務T1,將A賬戶減去50¥......,事務T2將A賬戶增加10%......,按照T1,T2的順序執行的,我們認爲其是串行的。
T1 |
T2 |
read(A) A=A-50 write(A) read(B) B=B+50 write(B) commit |
read(A) A=A+A*0.1 write(A) read(B) B=B-100 write(B) commit |
現在我們將其進行併發調度的到如下調度
T1 |
T2 |
read(A) A=A-50 write(A)
read(B) B=B+50 write(B) commit |
read(A) A=A+A*0.1 write(A)
read(B) B=B-100 write(B) commit |
可以通過計算可以得出第一個串行執行的調度與第二個併發調度得到的系統最終的A+B值是相同的。
事務的串行調度
事務按照先後順序進行執行
事務的併發調度
再宏觀上來事務是一個接一個的執行即串行進行的,但微觀其是交叉執行的,如以上的第二個調度
事務的可串行化
某一種併發調度再某種意義上等價於一個串行調度。如以上例子中的後者等價於前者。可串行化的併發調度一定爲正確的調度,但正確的調度不一定爲可串行化調度。
見如下例子。
T1 |
T2 |
read(A) A=A-50 write(A)
read(B) B=B+50 write(B) commit |
read(B) B=B-100 write(B)
read(A) A=A+100 write(A) commit |
0x111
這個併發調度,與串行調度<T1,T2>最終的到的結果是相同的。但是該調度不是可串行化的。後面將進一步說明。
我們先說下調度中指令中的衝突的概念。假設在並行調度S中,某連續指令I,J(進行read或wirte操作的指令),當這兩條指令引用的是不同的數據項的時候,交換該指令的順序是不會影響到系統最終的結果。但是若是引用相同數據項Q,則有很多情況。
1,當兩條指令都是read(Q) ,即使交換順序也無關緊要,因爲都是讀的相同的數據。
2,如果一條是write(Q),一條是read(Q),則其先後順序是重要的,在前面的事務應該先讀或則寫,在後面的事務應該後讀或寫。
3,兩條都是write(Q),先後順序也很重要不能隨便改動,因爲在後面的write(Q)指令必然會將前面的write(Q)值覆蓋,系統最後在 那個得到的也必然是最後的write(Q),因此與前面的一樣在前面的事務應該先執行write後面的則後執行。
衝突
當兩條指令爲不同事務操作相同的數據項的時候,任何至少有一條指令爲write()的時候,我們認爲這兩條指令是衝突的,因爲他們的順序會影響到系統最終的結果。當兩條指令都是read()的時候,則不會產生衝突。
衝突可串行化
看如下 調度1
T1 |
T2 |
read(A) write(A)
read(B)
write(B) |
read(A)
write(A)
read(B) write(B) |
我們可以進行一些非衝突指令交換得到新的調度。(T1:read(B) 與 T2:read(A) 交換,都爲讀且數據項不同)→(T1:write(B) 與 T2: write(A) 交換)→(T1:write(B) 與 read(A) 交換)。
T1 |
T2 |
read(A) write(A) read(B) write(B) |
read(A) write(A) read(B) write(B) |
如果調度S通過一系列非衝突指令交換得到調度S`,我們認爲其爲衝突等價的。當某調度衝突等價於串行調度我們認爲該調度爲衝突可串行化調度。故非衝突可串行化則爲,調度S可以經過一系列衝突指令交換得到一個串行化調度。前面說到正確的調度不一定是可串行化調度,前面的調度(0x111)就是一個典型的非衝突可傳性化的調度,但他是正確的調度,應爲該調度與串行化調度最終得到的系統值是相同的。
這裏我們基於判斷調度是否爲衝突可串行化的,給出了一個簡單的方法,(事務的)優先圖。
圖中事務T1箭頭指向T2,表明事務T1,先於事務T2執行。並且只有在如下情況下一個事務會箭頭指向另一個事務(假設操作都是同一個數據項Q)。
1,T2執行read或者write之前,T1執行write
2,T2執行write之前,T1執行read
(可自行理會,相當明瞭。)
當出現如下調度時,可產生優先圖。
T1 |
T2 |
read(Q) write(Q) |
write(Q)
read(Q) |
T1:在read(Q)之前,T2:write(Q) ,有T2→T1。T2:在read(Q)之前,T1write(Q) ,有T1→T2。
此時形成了所謂的環,我們並不知道誰先誰後。所以我們認爲該優先有環圖是非衝突可串行化的。而無環的優先圖則爲衝突可串行化的。
有了優先圖,於是我們就可以通過檢測圖中是否存在環路來判定調度的衝突可串行化。而且我們還能基於優先圖,進行拓撲排序算法得到事務的先後順序。
如圖優先圖我們可以得到兩種事務順序。{T1 T2 T3 T4}或{T1 T3 T2 T4}
五,事務的隔離級別
【注意:個人認爲事務的隔離級別比較重要的並且在很多地方用的着的。】
1,數據庫中幾種常見的不一致性
①丟失修改
T1 |
T2 |
read(Q)
write(Q-1) commited |
read(Q) write(Q-1) commited
|
考慮如下例子,火車售票總票數爲Q=10,A B兩人同時網上訂票,A讀取票時Q=10,B讀取票時Q=10,此時B預定票成功並將Q-1 = 9後寫入數據庫提交事務,而A在B提交事務後,也預定票成功Q = 10 -1 = 9,也寫入數據庫。此時數據庫總票數爲9張但是A,B兩個人都訂了票,從而導致數據庫數據不一致性。A數據對數據庫的修改被丟棄,從而導致了們所說的修改丟失。
②不可重複讀
事務T1從數據庫中讀取某數據項之後,事務T2對數據庫中的該項數據進行了更新或則修改導致,事務T1再次從數據庫中讀取該數據項時與先前讀取的數據結果不一致的情況叫做不可重複讀。
③髒讀
事務T1更改數據庫中的數據項Q並寫如磁盤但是事務並未提交,此時事務T2從數據庫中讀取該數據項Q,之後又因爲某些原因事務T1被撤銷,從而導致事務T2讀取錯誤的數據,叫做髒讀。
④幻讀
事務T1從數據庫中讀取關係中某些記錄後,另外的事務T2對關係中插入或者刪除某些記錄,當事務T1再次讀取關係的時候,關係中莫名奇妙的增加了記錄或則消失了記錄的現象叫做幻讀。
2,事務的隔離級別
【等級從低到高】
①未提交讀
允許讀取還未提交的數據項,不允許丟失修改。
②已提交讀
只允許讀取已提交的數據項,不允許修改丟失,允許重複讀錯誤。
③可重複讀
只允許讀取已提交數據項,不允許修改丟失,不允許不可重複讀錯誤。
④可串行化
保證調度是可串行調度。
各隔離等級下的要求
|
丟失修改 |
髒讀 |
重複讀錯誤 |
幻讀 |
未提交讀 |
不允許 |
允許 |
允許 |
允許 |
已提交讀 |
不允許 |
不允許 |
允許 |
允許 |
可重複讀 |
不允許 |
不允許 |
不允許 |
允許 |
可串行化 |
不允許 |
不允許 |
不允許 |
不允許 |
六,隔離級別的實現**
1,基於鎖的隔離級別實現
共享鎖(S)
某數據項被事務加上共享鎖(S)後,該數據項可以被該事務讀但是不允許寫。
排他鎖(X)
某數據項被加上排他鎖(X)後,該事務可以對該數據項進行讀寫操作。
相容矩陣
當某事務對某一數據項加上鎖後,其他事務再次申請該數據項上的鎖時,是否給予該事務申請的在該數據項上的鎖
見如下共享鎖和排他鎖相容矩陣 。當事務T1擁有Q上的共享鎖時,其他事務申請該數據項上的共享鎖時是能夠被授予的,但是若申請該數據項上的排他鎖時則不被授予。
|
共享鎖 |
排他鎖 |
共享鎖 |
授予 |
不授予 |
排他鎖 |
不授予 |
不授予 |
封鎖協議
協議其實就是一組規則規定。至於封鎖協議則是規定事務何時對數據項進行加鎖,解鎖的一組規則。
事務餓死現象
考慮一種情況,事務T1在數據項上Q上擁有共享鎖,接着事務T2申請Q上的排他鎖,根據相容矩陣可知道系統時拒絕的,因此事務T2不得不等到事務T1釋放在該數據項上的鎖在進行加鎖,T2等待。此時有來了一個事務T3,他申請數據項Q上的共享鎖,根據相容矩陣他被授予該鎖,此時T1釋放了Q上的共享鎖,T2再次嘗試獲取該數據項上的排他鎖,但是此時T3擁有該數據項上的共享鎖,所以T1有被拒絕,並且不得不等待T3,如果後面,又接着來了事務T4,T5....申請該數據項上的共享鎖,那麼T1豈不是永遠無法獲得該數據項上的排他鎖了,此時T1處於餓死狀態。我們當然不希望有這種情況,我們可以通過事務先來後到的順序來給予同一數據項上的鎖,來解決此類問題。
我們來簡單看下鎖管理器時如何實現,並避免餓死現象的發生 。
有圖我們可以看出鎖管理器通過鎖表,索引到各個數據項,其實通過散列來實現的,然後每個數據項又維護一個鏈表,該鏈表節點對應各個事務以及事務獲得的鎖,後來的事務則通過加在鏈表末尾,當事務釋放其擁有的鎖的時候事務從鏈表中刪除。我們看最下面的數據項E1,他維護一個T1 T5 T9 T2 的鏈表,紅色的字體表示是排他鎖,黑色的字體爲排他鎖。T1,T5申請的是共享鎖因此他們都獲得了該數據項上的鎖,並且沒有釋放,T9申請的是排他鎖,因此他等待他前面事務的鎖的釋放,T2雖然申請的共享鎖但是他前面有事務T9正在申請排他鎖,因此他要等到T9獲得鎖並釋放鎖才能獲得鎖。可見這裏T9不會出項餓死狀態,因此避免了前面提到的情況。
現在我們通過封鎖協議的加鎖與解鎖時機來實現四類隔離級別
1,未提交讀
當事務T1對數據項Q進行修改時,對其加上排他鎖防止其他事務對Q進行修改,並且修改完成後釋放該鎖。僅防止了修改丟失錯誤。 其也會可能會產生事務的級聯回滾。我們簡單來說一下級聯回滾,依據以上的讀在T1unlock(Q)那一刻,事務T2,獲得共享鎖並且read(Q),在這之後事務T1中止了,因此事務T1需要回滾,但T2讀取的數據時基於T1寫的Q(假設T1在對Q加鎖期間對Q進行寫操作),因此事務T2也要回滾這就時我們所說的級聯回滾,這個種情況也可以發生在多個事務之間。想象一下一種更加糟糕的情況,假若事務T2在T1中止之前就已經提交事務了,此時即使事務T1中止,事務T2也無法回滾了,我們認爲這樣的調度時不可恢復的調度,可恢復調度則與之相反。雖然這種情況是我們不希望看到的也可以預防,假若遇到這樣的情況我們也可以通過前面所說的補償事務來解決。
2,已提交讀
當事務T1對數據項Q進行修改時,對其加上排他鎖防止其他事務對Q進行修改,並且在該事務提交後釋放該鎖。可以防止修改丟失和髒讀,也不會發生級聯現象和不可恢復調度。但是事務T1在中間時刻已經完成了對write(Q),write(Q)之後即使事務T1沒有對Q進行任何操作任然佔着Q的資源,從而導致了資源上的浪費。
3,不可重複讀
有寫要求的數據項Q加上排他鎖直到事務提交時再釋放該鎖,有讀要求的數據項W加上共享鎖直到不再讀取時該事務釋放該鎖。防止修改丟失,髒讀和可重複讀錯誤。
4,可串行化
有寫要求的數據項Q加上排他鎖直到事務提交時再釋放該鎖,有讀要求的數據也加上排他鎖直到不再讀取該事務時釋放該鎖。防止修改丟失,髒讀,可重複讀錯誤和幻讀。
【注意:1,2並沒有使用共享鎖,3,4纔開始使用共享鎖】
很顯然,隔離等級越高資源浪費的越多,系統的效率也相對較低。
兩階段封鎖協議
將事務對數據項的加鎖和解鎖放在兩個階段。分別時增長階段和收縮階段,增長階段只允許加鎖,收縮階段只允許解鎖。在這兩個階段分界點爲封鎖點,也就是第一個解鎖的時刻,之後就不能再進行加鎖了。兩階段封鎖協議保證了調度是可串行化的,但是也同樣也可能存在死鎖和級聯的風險。
證明兩階段封鎖協議是可串行化的
證明過程可以用到前面的說過的優先圖,我們通過判斷是否成環來判斷是否是衝突可串行化的。假設事務集合S 滿足兩階段封鎖協議的,不妨再假設S中存在成環事務優先圖,即T1→T2→T3...→Tn→T1,則必定存在L1(A) U1(A) L2(A) L2(B) U2(A) U2(B) L3(B) L3(C) U3(B) U3(C) ... Ln(X) Ln(Y) Un(X) Un(Y) L1(Y) U1(Y) 。這裏我們假定事務T1和事務T2,有衝突操作即操作了相同的數據(其中一個爲寫操作) A 則T2必須等到T1釋放對A鎖才能操作A,同樣的T3等待T2釋放對B的鎖...T1等到Tn釋放對Y的鎖,由於事務是遵頊兩階段封鎖協議的即一個事務第一次釋放鎖後,不能再有加鎖動作,顯然這裏事務T1與之矛盾,所以事務集S中是不存在成環的優先圖的,也就是衝突可串行化的。
兩階段封鎖協議產生死鎖的情況
T1 |
T2 |
Lock(A) Read(A) Write(A)
Lock(B) …wait |
Lock(B) Read(B) Write(B) Lock(A) …..wait |
很顯然事務T1和事務T2都遵循兩階段封鎖協議,但是T2等待T1釋放A鎖,T1等待T2釋放B鎖,從而產生了死鎖的情況。
兩階段封鎖協議級聯的情況
T1 |
T2 |
T3 |
Lock(A) Read(A) Write(A) Unlock(A)
|
Lock(A) Read(A) Write(A) Unlock(A) |
Lock(A) Read(A) |
可以看到事務T1在還沒有提交的時候已經釋放了對於A的鎖然後是事務T2,T3,當事務T3執行Read操作之後然而事務T1因爲某些原因中止了而導致事務回滾此時的回滾是級聯的即T2,T3也必須回滾,因爲T2,T3都讀取了T1寫的數據。
兩階段封鎖協議防止級聯的一種方法是嚴格兩階段封鎖協議,即事務持有的排他鎖都在事務提交之後釋放。這樣保證了事務不能在別的事務持有該事務要訪問的數據的排他鎖釋放之前(即事務提交之前)獲得該鎖,這樣就防止了級聯的發生。還有一種叫做強兩階段封鎖協議,即事務的任何鎖(排他鎖或者共享鎖)必須在事務提交後釋放,強兩階段封鎖協議是按照提交的順序串行化的。
鎖轉化
可以想象一種場景,事務T1開始的時候對某寫數據項有讀的要求,由於之後還要對這些數據項進行寫。所以一開始就獲得這些數據項上的排他鎖,先對這些數據項進行讀操作,中間一段時間內不對這些數據項進行任何操作,之和再對這些數據項進行寫操作。由於該事務時遵頊兩階段封鎖協議的,事務T1只能在寫操作結束之後才能釋放這些數據項上的鎖,由於中間這段時間內持有排他鎖因此其他在他前面的事務不等併發的得這些數據項上的共享鎖,且必須等到其釋放鎖後方可讀,顯然這降低了併發度。於是我們想是不是可以一開始事務T1只對這些數據項上的共享鎖,之後有寫要求的時候在將其升級爲排他鎖,於是就有了鎖轉化。
鎖轉換有升級(共享鎖變排他鎖)和降級(排他鎖變共享鎖),且升級只能發生在增長階段,降級只能發生收縮階段。
兩階段封鎖協議小結
1,不存在修改丟失問題
2,髒讀情況。比如:T1(lock-X(A) write(A) unlock-X(A))→T2(lock-S(A) read(A) unlock-S(A)) →T1(...由於某些原因中止),T2也必須回滾否則髒讀(即級聯)。如果是嚴格兩階段封鎖協議則不會發生以上情況(即不會產生級聯現象,前面提到過)。強兩階段封鎖協議更加不會。
3,可重複讀錯誤情況,不會發生。
4,商業上一般使用的是嚴格兩階段封鎖協議加鎖轉換。保證事務無級聯且是可串行化的,也有較高的併發度(鎖轉換)。
多粒度
百度一下:粒度是指數據倉庫的數據單位中保存數據的細化或綜合程度的級別。細化程度越高,粒度級就越小;相反,細化程度越低,粒度級就越大。數據的粒度一直是一個設計問題。
我們簡單來了解一下粒度,之前我們討論的事務對數據的操作都是在數據項級別上的,也就是說粒度是也可以說是最小的,細化程度高嘛。我們之前也討論過幻讀現象,比如說事務T1在查看某張表中滿足條件的數據項並且對其加上了鎖,事務T2在關係中插入一個滿足T1查詢條件的數據,導致表中多出了一條數據,如產生幻覺一般。但是如果我們將鎖的粒度加大到表這個範圍(即將整個表鎖住),則不會產生這樣的情況,因爲T1已經持有表的鎖了,所以T2就不能在對該表進操作了。其實不僅有表鎖還有文件上的鎖,以及數據庫上的鎖等。
當我們想操作整個關係的時候,並且不想別的事務訪問,我們可以給這個關係加上排他鎖。但是有沒有想到一種情況 ,假若我想給整個數據庫DB加上鎖,比如排他鎖,但此時其他的事務已經對關係r1加上了排他鎖,所以我們不能成功的申請到對整個數據庫的排他鎖,那麼我們是怎麼知道r1上還有排他鎖的呢,我們必須便利整個數據庫,甚至是關係中的的每一個數據項是否加上了鎖,顯然這樣遍歷整個數據庫是我們不太像要的,因爲這樣的效率極低。因此我們想到了一個新的方法,即意向鎖。何爲意向鎖?意向鎖怎樣工作?待我後面娓娓道來。
意向鎖的含義是如果對一個結點加意向鎖,則說明該結點的下層結點正在被加鎖;對任一結點加鎖時,必須先對它的上層所有祖先結點加意向鎖。
有了意向鎖,當我們判斷是否可以對一個節點加上鎖時,我們可以先判斷想加鎖的節點的意向鎖是否與我們想給節點加的鎖互斥,然後看是否能獲得從根節點到目標節點路徑上的每一個 節點的意向鎖。總的來說我們一開始判斷目標節點是爲了檢查目標節點的所有子節點加上了什麼類型的鎖,而從根節點到目標節點路勁檢查時爲了檢查目標節點的所有父節點加上了什麼鎖,最後來判斷是否可以給其加上鎖。顯然這樣極大的節約了時間,因爲我們不需要掃描整個樹。
意向鎖類型
排他意向鎖(IX)
當一個節點加上了IX,我們認爲該節點的所有子節點中必然存在某個節點或者某些節點加上了排他鎖。
共享意向鎖(IS)
同樣的當一個節點加上了共享意向鎖時,我們認爲他的所有子節點中必然存在某個節點或某些節點加上了共享鎖。
共享排他意向鎖(SIX)
當一個事務想給一個節點加上共享鎖同時又想修改該節點下某個子節點,我們可以給該節點加上 SIX。
相容矩陣
|
共享鎖 |
排他鎖 |
共享意向鎖 |
排他意向鎖 |
共享排他意向鎖 |
共享鎖 |
授予 |
不授予 |
授予 |
不授予 |
不授予 |
排他鎖 |
不授予 |
不授予 |
不授予 |
不授予 |
不授予 |
共享意向鎖 |
授予 |
不授予 |
授予 |
授予 |
授予 |
排他意向鎖 |
不授予 |
不授予 |
授予 |
授予 |
不授予 |
共享排他意向鎖 |
不授予 |
不授予 |
授予 |
不授予 |
不授予 |
作爲一個簡單的示例,我們來簡單瞭解意向鎖的工作過程,用到上面的樹狀圖。
①某個事務T1申請關係r1上的排他鎖,他先檢查該節點上的意向鎖,顯然沒有任何鎖,然後看是否能獲得從根節點到該節點路徑上的所有節點的IX鎖,(如果不衝突的化)加上IX鎖,因爲此時沒有任何鎖所以該事務T1申請的鎖授予。
②當一個事務申請File2上的共享鎖時,有如下過程,先檢查File2上是否有鎖與之衝突,顯然沒有,然後獲得從根節點到該節點路徑上的所有節點的IS鎖,並判斷是否有與之衝突,比如這裏DB上有IX鎖,顯然與我想獲得IS鎖是無衝突的,最後授予FIle2S鎖。得到如下圖。
③此時某個事務申請File1上的共享鎖,先檢查File1上的鎖是否與之衝突,顯然IX與S是衝突的,所以該事務申請直接就失敗了。
死鎖
前面的例子中我們也提到過死鎖的情況,死鎖是在併發系統中非常常見的問題。有如下定義,在事務集合S中,每個事務Ti都在等待其他事務的鎖的情況,我們認爲出現了死鎖。
對於死鎖我們要們預防他的發生要麼發生後對其進行恢復。預防:比如說我們一旦發現某個事務在等待加鎖並可能發生死鎖,我 們立即回滾該事務。再比如說對某個事務我們一開始就獲得該事務的所有需要的鎖,然後進行操作。雖說這些方法是有效的但也是低效的。恢復:當某些事務發生死鎖時我們回滾其中的一個或某些事務使其恢復。還有一種介於預防和恢復之間的機制叫鎖超時,當某個事務等待某個鎖超過了一定的時間,我們就回滾該事務。實際上死鎖的處理有很多算法以及機制,這裏將的比較籠統。
2,基於時間戳的的協議
時間戳
不嚴謹的來說其實就是某個時刻,當事務T1啓動的時候,系統將該時刻給事務T1,即爲事務T1的時間戳TS(T1)。當有新的事務T2進來的時候,事務T2也被給予時間戳TS(T2),由於T1的時間戳比T2的時間戳小,因此事務T1必然是在事務T2,前面執行的事務。時間戳有唯一遞增的特點。利用時間戳可以進行併發控制。
如何利用時間戳進行併發控制,即多個事務併發執行的時候保證其衝突可串行性呢?這裏有一種簡單的規則。
R-timestamp(X) : 表示成功讀取數據項X的所有事務中最大事務時間戳。
W-timestamp(X) : 表示成寫入數據項X的所有事務中的最大事務時間戳。
①事務T1執行讀操作 read(X) 時
如果TS(T1) < W-timestamp(X) ,即存在事務T1後面的事務已經成功寫入數據項X,因此T1讀取數據過晚,系統拒絕T1的 read(X)請求,因爲T1 想讀取的數據應該是在他之前的事務寫入的數據項X的值,因此事務T1回滾,重啓。
如果TS(T1) >= W-timestamp(X),事務T1成功read(X),這裏等於說明是該事務本身,並且將R-timestamp(X)設置爲MAX(R- timestamp(X),TS(T1)),即更新R-timestamp(X)的值。
①事務T1執行讀操作 write(X) 時
如果TS(T1) < W-timestamp(X),即存在事務T1後面的事務已經成功寫入數據項X,因此T1寫入數據過晚,系統拒絕T1的 write(X) 請求,因爲事務T1讀取若寫入X將會覆蓋掉他後面的事務已經寫入的數據項,因此事務T1回滾重啓。
如果TS(T1) < R-timestamp(X) ,即存在事務T1後面的事務已經讀取了數據項X的值且讀的不是他寫入的值,並且認爲事務T1已 經不存在,因此T1寫入數據過晚,事務T1必須回滾,重啓。
如果文字描述的不夠清楚可以結合下面來理解。
基於時間戳協議的並行事務是不會發生死鎖的情況的。任何事務在執行指令的過程中總是會先判斷是否和先前的事務發生衝突,即根據上面的簡單的規則,一旦發生衝突立馬回滾,沒有等待,沒有等待就沒有死鎖。
基於時間戳的協議級聯並行事務會不能保證無級聯現象。某個事務發出read(Q)請求,發現最後寫Q的事務的時間戳在他之前,因此他可以read(Q)操作,但是由於某些原因之前的