資料庫中的併發操作帶來的一系列問題及解決方法
資料庫中常見的併發操作所帶來的一致性問題包括:丟失的修改、不可重複讀、讀髒資料、幻影讀(幻影讀在一些資料中往往與不可重複讀歸為一類)。
丟失修改
下面我們先來看一個例子,說明併發操作帶來的資料的不一致性問題。
考慮飛機訂票系統中的一個活動序列:
甲售票點(甲事務)讀出某航班的機票餘額A,設A=16.
乙售票點(乙事務)讀出同一航班的機票餘額A,也為16.
甲售票點賣出一張機票,修改餘額A←A-1.所以A為15,把A寫回資料庫.
乙售票點也賣出一張機票,修改餘額A←A-1.所以A為15,把A寫回資料庫.
結果明明賣出兩張機票,資料庫中機票餘額只減少1。
歸納起來就是:兩個事務T1和T2讀入同一資料並修改,T2提交的結果破壞了T1提交的結果,導致T1的修改被丟失。前文(2.1.4資料刪除與更新)中提到的問題及解決辦法往往是針對此類併發問題的。但仍然有幾類問題通過上面的方法解決不了,那就是:
不可重複讀
不可重複讀是指事務T1讀取資料後,事務T2執行更新操作,使T1無法再現前一次讀取結果。具體地講,不可重複讀包括三種情況:
事務T1讀取某一資料後,事務T2對其做了修改,當事務1再次讀該資料時,得到與前一次不同的值。例如,T1讀取B=100進行運算,T2讀取同一資料B,對其進行修改後將B=200寫回資料庫。T1為了對讀取值校對重讀B,B已為200,與第一次讀取值不一致。
事務T1按一定條件從資料庫中讀取了某些資料記錄後,事務T2刪除了其中部分記錄,當T1再次按相同條件讀取資料時,發現某些記錄神密地消失了。
事務T1按一定條件從資料庫中讀取某些資料記錄後,事務T2插入了一些記錄,當T1再次按相同條件讀取資料時,發現多了一些記錄。(這也叫做幻影讀)
讀"髒"資料
讀"髒"資料是指事務T1修改某一資料,並將其寫回磁碟,事務T2讀取同一資料後,T1由於某種原因被撤消,這時T1已修改過的資料恢復原值,T2讀到的資料就與資料庫中的資料不一致,則T2讀到的資料就為"髒"資料,即不正確的資料。
產生上述三類資料不一致性的主要原因是併發操作破壞了事務的隔離性。併發控制就是要用正確的方式排程併發操作,使一個使用者事務的執行不受其它事務的干擾,從而避免造成資料的不一致性。
併發一致性問題的解決辦法
封鎖(Locking)
封鎖是實現併發控制的一個非常重要的技術。所謂封鎖就是事務T在對某個資料物件例如表、記錄等操作之前,先向系統發出請求,對其加鎖。加鎖後事務T就對該資料物件有了一定的控制,在事務T釋放它的鎖之前,其它的事務不能更新此資料物件。
基本的封鎖型別有兩種:排它鎖(Exclusive locks 簡記為X鎖)和共享鎖(Share locks 簡記為S鎖)。
排它鎖又稱為寫鎖。若事務T對資料物件A加上X鎖,則只允許T讀取和修改A,其它任何事務都不能再對A加任何型別的鎖,直到T釋放A上的鎖。這就保證了其它事務在T釋放A上的鎖之前不能再讀取和修改A。
共享鎖又稱為讀鎖。若事務T對資料物件A加上S鎖,則其它事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這就保證了其它事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。
封鎖協議
在運用X鎖和S鎖這兩種基本封鎖,對資料物件加鎖時,還需要約定一些規則,例如應何時申請X鎖或S鎖、持鎖時間、何時釋放等。我們稱這些規則為封鎖協議(Locking Protocol)。對封鎖方式規定不同的規則,就形成了各種不同的封鎖協議。下面介紹三級封鎖協議。三級封鎖協議分別在不同程度上解決了丟失的修改、不可重複讀和讀"髒"資料等不一致性問題,為併發操作的正確排程提供一定的保證。下面只給出三級封鎖協議的定義,不再做過多探討。
1級封鎖協議
1級封鎖協議是:事務T在修改資料R之前必須先對其加X鎖,直到事務結束才釋放。事務結束包括正常結束(COMMIT)和非正常結束(ROLLBACK)。1級封鎖協議可防止丟失修改,並保證事務T是可恢復的。在1級封鎖協議中,如果僅僅是讀資料不對其進行修改,是不需要加鎖的,所以它不能保證可重複讀和不讀"髒"資料。
2級封鎖協議
2級封鎖協議是:1級封鎖協議加上事務T在讀取資料R之前必須先對其加S鎖,讀完後即可釋放S鎖。2級封鎖協議除防止了丟失修改,還可進一步防止讀"髒"資料。
3級封鎖協議
3級封鎖協議是:1級封鎖協議加上事務T在讀取資料R之前必須先對其加S鎖,直到事務結束才釋放。3級封鎖協議除防止了丟失修改和不讀'髒'資料外,還進一步防止了不可重複讀。
事務隔離級別
儘管資料庫理論對併發一致性問題提供了完善的解決機制,但讓程式設計師自己去控制如何加鎖以及加鎖、解鎖的時機顯然是很困難的事情。索性絕大多數資料庫以及開發工具都提供了事務隔離級別,讓使用者以一種更輕鬆的方式處理併發一致性問題。常見的事務隔離級別包括:ReadUnCommitted、ReadCommitted、RepeatableRead和Serializable四種。不同的隔離級別下對資料庫的訪問方式以及資料庫的返回結果有可能是不同的。我們將通過幾個實驗深入瞭解事務隔離級別以及SQL Server在後臺是如何將它們轉換成鎖的。
Serializable
Serializable隔離級別是最高的事務隔離級別,在此隔離級別下,不會出現讀髒資料、不可重複讀和幻影讀的問題。在詳細說明為什麼之前首先讓我們看看什麼是幻影讀。
所謂幻影讀是指:事務1按一定條件從資料庫中讀取某些資料記錄後,事務2插入了一些符合事務1檢索條件的新記錄,當事務1再次按相同條件讀取資料時,發現多了一些記錄。
repeatable read
1:所有的select在第一次一致讀以後在事務中都會使用一樣的資料狀態快照。
2:update,delete都會使用間隙鎖來保證資料的安全。防止phantom。
3:這是採用最廣的事務隔離級別,也是mysql預設的事務隔離級別。
read commited
1:每一個select都會使用各自的資料狀態的快照。
2:如果當前的資料狀態已更新到最新,但是當單個select的時候仍然會產生不一致的資料狀態。
3:更少的間隙鎖意味著更少的死鎖。
4:唯一key的檢查在第二索引和其它外來鍵檢查的時候也會產生間隙所。(gap必須被鎖定以防止在parent row被刪除後仍在child row中插入相關資料)。
5:這種隔離級別也是使用的非常普遍的隔離級別尤其是在5.1以後的版本中。
6:徵對在5.0更早的版本中,可以通過innodb_locks_unsafe_for_binlog移除gap locking。
(In V5.1, most gap-locking is removed w/ this level, but you MUST use row-based logging/replication。)
read uncommitted
1:這種隔離級別幾乎不被使用,在select將會看到各種奇怪的資料現象,當然包括其它事務還未提交的資料。
2:強烈不推薦,不能保證資料的一致性。