一、事務併發的問題
這些問題可以歸結爲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