數據庫訪問是通過事務完成的,首先我們搞清楚什麼是事務?
被視爲整體的一組工作
這組工作要麼完全完成,要麼全部不完成,不存在部分完成情況
真實生活中以轉賬說明事務:
- 第一步,從賬戶A中減去X元金額;
- 第二步,將X元金額存入賬戶B
這些多步操作必須全部完整完成,不能半途而廢。數據庫事務的工作方式與此相同。他們能保證,無論發生什麼事情,數據的操作處理都被看成是原子的(你永遠不會看到“轉變一半”的情況)。
原子性是DBMS維護ACID屬性的一部分,ACID包括:
-
Atomicity原子性: 事務中所有操作要麼全部發生,要麼全部不。
-
Consistency一致性: 數據庫從一致狀態開始到一致狀態結束。
-
Isolation隔離性: 一個事務的執行是隔離於其他事務的。
- Durability:如果事務已經確認提交,其效果是持久保存在數據庫中。
那麼在併發訪問數據庫的情況下會發生什麼問題?併發流程可能會改變隔離性和一致性這兩個屬性。讓我們假設兩個用戶預訂了一架飛機的同一個座位:
- 客戶1 發現一個空座位
- 客戶2 發現同樣的空座位
- 客戶1預訂了這個座位
- 客戶2也同時預訂了這個座位
我們將事務的順序執行稱爲schedule調度,它表示基於時間先後的一系列事務執行,在這裏客戶1和客戶2分別存在兩個事務,這個兩個事務同時發生,我們需要通過串行化serializability來保證事務正確執行,也就是說,需要通過一個調度來實現併發控制機制。
一個調度包含下面一些操作:
read R(X)
write W(X)
commit (所有操作完成後,這些操作應該被確認和記錄)
abort (在執行一部分操作後,如果我們退出,所有操作應該沒有一個被確認完成或記錄保存)
爲了有完整的調度,commit 或abort是被強制的。
serial schedule串行調度是事務執行時間沒有交織,所有操作都是順序執行。
當下麪條件滿足,Conflicting operation衝突操作會出現:
- 它們屬於不同的事務
- 它們得訪問同一的對象X
- 至少其中一個操作是W(X) (對X的寫操作)
讓我們看看下面這些衝突操作:
- 寫讀衝突 Write-Read Conflict: 讀到一個未提交uncommitted的數據
- 讀寫衝突 Read-Write Conflict: 首次讀以後,再讀已經被修改的數據。
- 寫寫衝突 Write-Write Conflict:其中一個寫操作丟失
Write-Read Conflict, 也稱爲reading uncommitted data讀未提交的數據或髒都dirty-read,當一個事務T2試圖讀取數據庫對象A,但是其已經被事務T1修改,還沒有提交確認,當T1繼續其事務時,對象A的數據已經不一致了,如下圖:
換句話說,髒讀是當一個事務讀取了被另外一個事務修改但是還沒有提交確認的表記錄。
讀寫衝突Read-Write Conflict, 也稱爲unrepeatable reads, ,當一個事務T1讀兩次數據庫對象A,第一讀以後事務T1等待事務T2完成,T2覆蓋重寫了對象A,當T1再次讀A時,A數據存在兩個不同版本,T1被強迫退出,因爲這是不可重複的讀。
真實案例:Bob和Alice是票務員,他們要預訂一個表演票,只剩餘一張了,Alice登錄進入,發現這周票比較貴,猶豫了一下,而Bob登錄進入後,就立即買了這張票,然後退出。Alice決定買這張票時,發現已經沒有多餘的票了。
寫寫衝突Write-Write Conflict, 也稱爲覆蓋未提交數據overwriting uncommitted data,它是發生在當有一個丟失修改情況下,試圖使這種場景串行只能是下面兩者之一:要麼是事務T1版本,要麼是事務T2的版本:
一旦併發事務應用到數據庫上,使用調度確保串行化,也就是執行是順序的,不會有時間的重疊,除了串行,還有以下幾種調度方式:
ACA : 避免級聯中斷
可恢復性recoverable
嚴格調度strict schedule
如果一個調度是串行化的,最好的驗證辦法是通過依賴圖。爲了建立依賴圖,我們需要下面過程:
-
每個節點每個事務的代表
-
在事務Tx寫入後還有另外的事務Ty讀嗎?如果是,從節點Tx畫線到節點Ty
-
在事務Tx讀取以後,是否有其他事務再進行寫入呢?如果是,從Tx畫線到Ty。
- 在Tx寫入後有其他事務再次寫入嗎?如果是,從Tx畫線到Ty。
如果退出事務,不要忘記移除你之前的畫線。
爲了偶一個串行化的調度,依賴圖得是非循環的,也就是不能首尾循環依賴。比如下面的調度不是串行化:
而下面的調度是串行化的:
現在我們知道什麼時候一個調度是 strict嚴格的?
當一個對象被事務T寫入,那麼一直到事務T確認提交或退出,這個對象就一直不能被再次讀取或寫入了。
那麼一個調度如何避免級聯退出呢?
當一個操作只能讀取已經被提交確認的數據時,就可以避免級聯一個個的事務退出。
那麼如何知道一個調度是可恢復的?
對於每個事務,當事務Ty讀取一些Tx事務的寫入數據時,Tx的Commit提交操作出現在Ty的Commit提交操作之前。
總結
併發控制是通過串行化方式實現,這樣能夠確保任何非串行化的執行不會發生。當然主要問題是帶來性能瓶頸。
寫在最後:
歡迎大家關注我新開通的公衆號【風平浪靜如碼】,海量Java相關文章,學習資料都會在裏面更新,整理的資料也會放在裏面。
覺得寫的還不錯的就點個贊,加個關注唄!點關注,不迷路,持續更新!!!