數據庫事務並發問題
數據庫的操作通常為寫和讀,就是所說的CRUD:增加(Create)、讀取(Read)、更新(Update)和刪除(Delete)。
事務就是一件完整要做的事情。
事務是恢復和並發控制的基本單位。
事務必須始終保持系統處於一致的狀態,不管在任何給定的時間並發事務有多少。
事務在關系數據庫中,一個事務可以是一條SQL語句,一組SQL語句或整個程序。是數據庫中各種數據項的一個程序執行單元。
事務是用戶定義的一個操作序列(多個表同時讀寫)。這些操作要么都做,要么都不做,是一個不可分割的工作單位。
事務通常是以BEGIN TRANSACTION開始,以COMMIT或ROLLBACK結束。
COMMIT:表示事務完成提交,即提交事務的所有操作,具體地說就是將事務中所有對數據庫的更新寫回到磁盤上的物理數據庫中去,事務正常結束。
ROLLBACK:表示事務的回滾,即在事務運行的過程中發生了某種故障,事務不能繼續進行,系統將事務中對數據庫的所有以完成的操作全部撤消,滾回到事務開始的狀態或設置的回滾點。
多個事務同時訪問數據庫時候,會發生下列5類問題,包括3類數據讀問題(臟讀,不可重復讀,幻讀),2類數據更新問題(第一類丟失更新,第二類丟失更新):
(http://blog.csdn.net/zhangzeyuaaa/article/details/46400419 該博文有具體例子)
第一類丟失更新
A事務撤銷時,把已經提交的B事務的更新數據覆蓋了。這種錯誤可能造成很嚴重的問題,通過下面的賬戶取款轉賬就可以看出來:
| 時間 |
取款事務A |
轉賬事務B |
| T1 |
開始事務 |
|
| T2 |
|
開始事務 |
| T3 |
查詢賬戶余額為1000元 |
|
| T4 |
|
查詢賬戶余額為1000元 |
| T5 |
|
匯入100元把余額改為1100元 |
| T6 |
|
提交事務 |
| T7 |
取出100元把余額改為900元 |
|
| T8 |
撤銷事務 |
|
| T9 |
余額恢復為1000 元(丟失更新) |
|
A事務在撤銷時,“不小心”將B事務已經轉入賬戶的金額給抹去了。
第二類丟失更新
A事務覆蓋B事務已經提交的數據,造成B事務所做操作丟失:
| 時間 |
轉賬事務A |
取款事務B |
| T1 |
|
開始事務 |
| T2 |
開始事務 |
|
| T3 |
|
查詢賬戶余額為1000元 |
| T4 |
查詢賬戶余額為1000元 |
|
| T5 |
|
取出100元把余額改為900元 |
| T6 |
|
提交事務 |
| T7 |
匯入100元 |
|
| T8 |
提交事務 |
|
| T9 |
把余額改為1100 元(丟失更新) |
|
上面的例子里由於支票轉賬事務覆蓋了取款事務對存款余額所做的更新,導致銀行最后損失了100元,相反如果轉賬事務先提交,那么用戶賬戶將損失100元。
共享鎖和排它鎖
為了解決並發問題,數據庫系統引入鎖機制。
基本的封鎖類型有兩種: 排它鎖(Exclusive locks 簡記為X鎖) 和 共享鎖(Share locks 簡記為S鎖)。
- 排它鎖又稱為寫鎖。若事務T對數據對象A加上X鎖,則只允許T讀取和修改A,其它任何事務都不能再對A加任何類型的鎖,直到T釋放A上的鎖。這就保證了其它事務在T釋放A上的鎖之前不能再讀取和修改A。
- 共享鎖又稱為讀鎖。若事務T對數據對象A加上S鎖,則事務T可以讀A但不能修改A,其它事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這就保證了其它事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。
封鎖粒度
數據庫中為了實現並發控制而采用封鎖技術。
封鎖對象的大小稱為封鎖粒度(Granularity)。
封鎖的對象可以是邏輯單元,也可以是物理單元。以關系數據庫為例子,封鎖對象可以是這樣一些邏輯單元:屬性值、屬性值的集合、元組、關系、索引項、整個索引項直至整個數據庫;也可以是這樣的一些物理單元:頁(數據頁或索引頁)、物理記錄等。
封鎖協議與隔離級別
在運用 排他鎖 和 共享鎖 對數據對象加鎖時,還需要約定一些規則,例如何時申請 排他鎖 或 共享鎖、持鎖時間、何時釋放等。稱這些規則為封鎖協議(Locking Protocol)。對封鎖方式規定不同的規則,就形成了各種不同的封鎖協議。不同的封鎖協議對應不同的隔離級別。
一級封鎖協議(對應read uncommited)
一級封鎖協議是:事務T在修改數據R之前必須先對其加X鎖,直到事務結束才釋放。事務結束包括正常結束(COMMIT)和非正常結束(ROLLBACK)。
一級封鎖協議可防止丟失更新,並保證事務T是可恢復的。
在一級封鎖協議中,如果僅僅是讀數據不對其進行修改,是不需要加鎖的,所以它不能保證可重復讀和不讀“臟”數據。
二級封鎖協議(對應read commited)
二級封鎖協議是:一級封鎖協議加上事務T在讀取數據R之前必須先對其加S鎖,讀完后即可釋放S鎖(瞬間S鎖)。
二級封鎖協議除防止了丟失更新,還可進一步防止讀“臟”數據。
三級封鎖協議(對應reapetable read)
三級封鎖協議是:一級封鎖協議加上事務T在讀取數據R之前必須先對其加S鎖,直到事務結束才釋放。
三級封鎖協議除防止了丟失更新和不讀‘臟’數據外,還進一步防止了不可重復讀和覆蓋更新。
四級封鎖協議(對應serialization)
四級封鎖協議是對三級封鎖協議的增強,其實現機制也最為簡單,直接對 事務中 所 讀取 或者 更改的數據所在的表加表鎖,也就是說,其他事務不能 讀寫 該表中的任何數據。這樣五類並發問題都得以避免!
注:封鎖協議和隔離級別並不是嚴格對應的。
ANSI SQL 92標准定義了4個等級的事務隔離級別:
SELECT @@tx_isolation;
SELECT @@global.tx_isolation;
SELECT @@session.tx_isolation;
SQL的標准定義了4個隔離級別,設定一定的規則,限定事務內部的那些改變是可見的,那些是不可見的。低級別的事務一般會支持更高的並發處理,涉及更低的系統開銷。
Read Uncommitted【讀未提交數據】:允許所有事務讀取未被其他事務提交的數據修改。會導致臟讀、不可重復讀和幻讀的問題的出現。
Read Committed【讀已提交數據】:只允許事務讀取已經被其他事務提交的數據修改。 Oracle和sql server默認的級別,可以避免臟讀,但不可重復讀和幻讀問題仍然會出現。
Repeatable Read【可重讀】:是 MySQL的默認事務隔離級別,它確保同一事務的多個實例在並發讀取數據時,會看到同樣的數據行。但幻讀問題未解除。
Serializable【可串行化】:最高的隔離級別,它通過強制事務排序,使之不可能相互沖突,從而解決幻讀問題。它是在每個讀的數據行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似於write_condition機制的其實都是提供的樂觀鎖。
兩種鎖各有優缺點,不可認為一種好於另一種,像樂觀鎖適用於寫比較少的情況下,即沖突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果經常產生沖突,上層應用會不斷的進行retry,這樣反倒是降低了性能,所以這種情況下用悲觀鎖就比較合適。
