事務併發的可能問題與其解決方案

一、事務併發的問題

這些問題可以歸結爲5類,包括3類數據讀問題( 髒讀、 不可重複讀和 幻象讀)以及2類數據更新問題( 第一類丟失更新和 第二類丟失更新)

髒讀(dirty read) 

A事務讀取B事務尚未提交的更改數據,並在這個數據的基礎上操作。如果恰巧B事務回滾,那麼A事務讀到的數據根本是不被承認的。來看取款事務和轉賬事務併發時引發的髒讀場景:

在這個場景中,B希望取款500元而後又撤銷了動作,而A往相同的賬戶中轉賬100元,就因爲A事務讀取了B事務尚未提交的數據,因而造成賬戶白白丟失了500元。在Oracle數據庫中,不會發生髒讀的情況。  

不可重複讀(unrepeatable read) 

不可重複讀是指 A事務讀取了B事務已經提交的更改數據。假設A在取款事務的過程中,B往該賬戶轉賬100元,A兩次讀取賬戶的餘額發生不一致: 

這個就我自己覺得問題不大,好像也並沒有影響什麼。

幻象讀(phantom read) 

A事務讀取B事務提交的新增數據,這時A事務將出現幻象讀的問題。幻象讀一般發生在計算統計數據的事務中,舉一個例子,假設銀行系統在同一個事務中,兩次統計存款賬戶的總金額,在兩次統計過程中,剛好新增了一個存款賬戶,並存入100元,這時,兩次統計的總金額將不一致:  

如果新增數據剛好滿足事務的查詢條件,這個新數據就進入了事務的視野,因而產生了兩個統計不一致的情況。 

不可重複讀 和 幻讀, 這兩者確實非常相似。不可重複讀 主要是說多次讀取一條記錄, 發現該記錄中某些列值被修改過。而幻讀 主要是說多次讀取一個範圍內的記錄(包括直接查詢所有記錄結果或者做聚合統計), 發現結果不一致(標準檔案一般指記錄增多, 記錄的減少應該也算是幻讀)。

第一類丟失更新

A事務撤銷時,把已經提交的B事務的更新數據覆蓋了。這種錯誤可能造成很嚴重的問題,通過下面的賬戶取款轉賬就可以看出來: 

第二類丟失更新 

A事務覆蓋B事務已經提交的數據,造成B事務所做操作丟失:  

 

二、事務隔離級別

爲了解決多個事務併發會引發的問題,進行併發控制。數據庫系統提供了四種事務隔離級別供用戶選擇。

1.Read Uncommitted(讀未提交): 一個事務不能修改其他事務正在修改的數據,但可以讀取到其他事務中尚未提交的修改,這些修改如果未被提交,將會成爲髒數據,Oracle有Ver控制,不會有髒讀。

2.Read committed(讀已提交):只允許讀取已經被提交的數據,反過來講,如果一個事務修改了某行數據且尚未提交,而第二個事務要讀取這行數據的話,那麼是不允許的。在MySql的InnoDB下,雖然這種操作不被允許,但MySQL不會阻塞住數據的查詢操作,而是會查詢出數據被修改之前的備份,返回給客戶端。MySQL的這種機制稱爲MVCC(多版本併發控制),就是說數據庫在事務併發的過程中對數據維護多個版本,使得不同的事務對不同的數據版本進行讀寫(MVCC的實現參見引用中的文章)。這樣的機制反映在應用中就是,在任何時候對數據庫查詢總是可以得到數據庫中最近提交的數據。爲被提交的髒數據被隔離起來,無法被查詢到,即防止髒讀發生。

3.Repeat Read(可重複讀): Repeat Read又比Read Committed更加嚴格一點,但仍然是在二級封鎖協議的範疇,只是讀取過程受到更多MVCC的影響。在Read Committed下,允許一個事務中多次相同查詢得到不同的結果,就是所謂的不可重複讀問題。這在一些應用中是允許的,所以oracle、SQL server上默認這一隔離級別,但MySQL沒有,它默認Repeat Read級別。在這一級別下,有賴於MVCC,同一個事務中的查詢只能查到版本號不高於當前事務版本的數據,即事務只能看到該事務開始前或者被該事物影響的數據。反過來說,這一級別下,不允許事務讀取在該事務開始後新提交的數據。即防止了不可重複讀的發生。

依靠上面的機制,已經做到了在事務內數據內容的不變,但是不能保證多次查詢得到的數據數量一致。因爲在一個事務執行的過程中別的事務完全可以執行數據插入,當插入了剛好符合查詢條件的數據時,就會引發數據查詢結果集增加,引發幻讀。還有一種情況就是,如果一個事務想插入一條數據,而另一個事務已經插入了含有相同主鍵的數據,那麼當前事務也會被阻塞,並最終執行失敗,雖然當前事務根本無法查詢到這一條數據,這也是一種幻讀。

4.Serializable(可串行化): 最強事務隔離機制Serializable,它遵循三級封鎖協議,使得所有的事務必須串行化執行,只要有事務在對錶進行查詢,那麼在此事務提交前,任何其他事務的修改都會被阻塞。這解決了一切併發問題,但會造成大量的等待、阻塞甚至死鎖,使系統性能降低,一般採用Repeat Read和數據庫鎖相結合方式來替代它。

 

讀未提交

事務讀不阻塞其他事務讀和寫,事務寫阻塞其他事務寫但不阻塞讀。

可以通過寫操作加“持續-X鎖”實現。

讀已提交

事務讀不會阻塞其他事務讀和寫,事務寫會阻塞其他事務讀和寫。

可以通過寫操作加“持續-X”鎖,讀操作加“臨時-S鎖”實現。

可重複讀

事務讀會阻塞其他事務事務寫但不阻塞讀,事務寫會阻塞其他事務讀和寫。

可以通過寫操作加“持續-X”鎖,讀操作加“持續-S鎖”實現。

串行化

“行級鎖”做不到,需使用“表級鎖”。

可串行化

如果一個並行調度的結果等價於某一個串行調度的結果,那麼這個並行調度是可串行化的。

區分事務隔離級別是爲了解決髒讀、不可重複讀和幻讀三個問題的。

事務隔離級別 回滾覆蓋 髒讀 不可重複讀 提交覆蓋 幻讀
讀未提交 x 可能發生 可能發生 可能發生 可能發生
讀已提交 x x 可能發生 可能發生 可能發生
可重複讀 x x x x 可能發生
串行化 x x x x x

 

三、常用的解決方案

1. 三級加鎖協議

一級封鎖協議:事務在修改數據前必須加X鎖,直到事務結束(提交或終止)纔可釋放;如果僅僅是讀數據,不需要加鎖。。事務結束包括正常結束(COMMIT)和非正常結束(ROLLBACK)。 一級封鎖協議保證事務T是可恢復的,並且可以防止兩個事務同時操作(增,刪,改)同一數據問題。在一級封鎖協議中,如果僅僅是讀數據不會加鎖的,所以它不能保證可重複讀和不讀“髒”數據。

二級封鎖協議:一級封鎖協議加上事務T在讀取數據R之前必須先對其加S鎖,讀完後方可釋放S鎖。 二級封鎖協議除防止了丟失修改,還可以進一步防止讀“髒”數據。但在二級封鎖協議中,由於讀完數據後即可釋放S鎖,所以它不能保證可重複讀。

三級封鎖協議 :一級封鎖協議加上事務T在讀取數據R之前必須先對其加S鎖,直到事務結束才釋放。 三級封鎖協議除防止了丟失修改和不讀“髒”數據外,還進一步防止了不可重複讀。

2. 兩段鎖協議(2-phase locking)

加鎖階段:事務在讀數據前加S鎖,寫數據前加X鎖,加鎖不成功則等待。

解鎖階段:一旦開始釋放鎖,就不允許再加鎖了。

若併發執行的所有事務均遵守兩段鎖協議,則對這些事務的任何併發調度策略都是可串行化的。

遵循兩段鎖協議的事務調度處理的結果是可串行化的充分條件,但是可串行化並不一定遵循兩段鎖協議。

 

 

四、不同的事務隔離級別與其對應可選擇的加鎖協議

事務隔離級別

加鎖協議

讀未提交

一級加鎖協議

讀已提交

二級加鎖協議

可重複讀

三級加鎖協議

串行化

兩段鎖協議

參考文章:

https://www.cnblogs.com/jmcui/p/9812679.html

https://blog.csdn.net/dingguanyi/article/details/80888441

 

 

發佈了184 篇原創文章 · 獲贊 16 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章