解析事務隔離(事務隔離是如何解決髒讀、幻讀、不可重複讀等問題)

前言

鎖機制和索引是數據庫兩塊極其重要的知識點,也是面試中面試官最愛問的兩個部分。前面的文章已經對索引知識有了一個較爲全面的解析,這篇文章主要從併發事務的角度出發,解析併發事務帶來的問題,以及事務隔離是如何解決併發事務帶來的問題。

文章中會涉及到MVCC(多版本併發控制)和數據庫的鎖機制以及鎖協議相關知識,若不是很瞭解可以翻閱我其它兩篇有關這方面的博文。MVCC(多版本併發控制)-----《InnoDB的MVCC實現原理(InnoDB如何實現MVCC以及MVCC的工作機制)》鎖機制和鎖協議-----《解析數據庫鎖協議和InnoDB的鎖機制》

事務

事務存在意義是保證數據的一致性。事務保證了事務內的一系列操作,要不都執行,要不都不執行。例如A賬號給B賬戶轉賬10000,則A賬號減去10000,B賬號增加10000,這兩個操作必須是都成功執行或者都得不到執行。

事務的四個特性(ACID)

事務具有原子性、一致性、持久性、隔離性這四個特性,事務通過確保原子性、隔離性、持久性來確保事務的一致性。

原子性(Atomicity)

事務的原子性是指事物是最小的執行單位,不允許分割,事務內的一系列操作,要不都執行,要不都不執行。

一致性(Consistency)

事物的提交只會導致數據庫的狀態從一個一致性狀態到另外一個一致性狀態,換句話來說就是事務一系列操作的中間操作導致的中間不一致狀態不會讓別的事務看到。一致性狀態指的是多個事務訪問到的數據庫的數據和數據結構是一致的。

隔離性(Isolation)

事務的隔離性是指併發事務在執行過程中不能相互影響,其對數據庫的影響和它們串行執行時一樣。(數據庫綜合考慮性能和數據一致性,提出了四個隔離級別,可串行化是最高隔離級別)

持久性(Durability)

一個事務被提交之後。它對數據庫中數據的改變是持久的,即使數據庫發生故障也不會導致提交改變結果的丟失。

事務特性分析

事務具有四個特性,但其實事務存在的意義就是保證事務的數據以及數據結構的一致性和數據完整性。因此四個特性中最重要的其實是一致性,原子性和隔離性的存在都是爲了保證事務的一致性。

破壞數據(數據結構)一致性的行爲主要是兩個方面,一是併發事務的執行,另外一個是事務或者數據庫故障。數據庫系統是通過併發控制來避免併發事務執行帶來的數據不一致性問題,通過日誌恢復技術來處理數據庫故障帶來的數據不一致性問題。併發控制技術保證了事務的隔離性,使數據庫的一致性狀態不會因爲併發執行的操作被破壞。日誌恢復技術保證了事務的原子性,使一致性狀態不會因事務或系統故障被破壞。同時使已提交的對數據庫的修改不會因系統崩潰而丟失,保證了事務的持久性。

併發事務執行帶來的問題

髒讀

髒讀指的是事務讀取了另外一個事務未提交的數據,但因爲事務回滾,所以該事務讀取的是髒數據。當A事務正在訪問數據並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時B事務也訪問了這個未提交的數據,然後使用了這個數據。因爲這個數據是還沒有提交的數據,A事務有可能會回滾,那麼B事務讀到的這個數據可能是“髒數據”,而依據“髒數據”所做的操作可能是不正確的。
在這裏插入圖片描述
事務2在事務1未提交時,讀取了餘額5000,而事務1出錯回滾,因此此時餘額應該是10000,但事務2因爲已經讀取過一次,因此不會在事務1回滾之後再讀取餘額值,事務2讀取到的餘額依舊是5000,此時事務2讀取的就是髒數據,事務2在此基礎上做的修改就是錯誤的,事務2再存入10000,事實應該是10000+10000=20000,但因爲讀取的是髒數據,所以存入之後是5000+10000=15000,更新的結果就是錯誤的。

丟失更新

第一類丟失更新

在沒有事務隔離的情況下,兩個事務同時更新一行數據,但是第二個事務卻中途失敗回滾, 導致兩個事務對數據的修改都失效了。例如一行數據有A和B兩個字段

時間順序 事務1 事務2
1 開始事務
2 開始事務
3 讀取A值30
4 讀取B值20
5 A=A-10
6 B=B-20
7 提交事務
8 事務回滾

事務2事務回滾,導致這一行數據都恢復到事務之前的值,導致A和B的值都回滾到初始狀態,A值爲30,B值爲20。事務1的更新丟失。

第二類丟失更新

第二類丟失更新是不可重複讀的特例,事務在對數據進行修改的過程中覆蓋了其它事務對該數據的修改,導致其它事務修改丟失

時間順序 事務1 事務2
1 開始事務
2 開始事務
3 讀取A值30
4 讀取A值30
5 A=A+10
6 A=A-20
7 提交事務
8 提交事務

數據庫中最終A的值爲10,事務2對A值的修改覆蓋了事務1對A值的修改,彷彿事務1沒有發生一樣。

不可重複讀

指在一個事務內多次讀同一數據,但在這個事務還沒有結束時,另一個事務也訪問該數據並且有修改數據行爲。那麼,在第一個事務中的兩次讀數據之間,由於第二個事務的修改導致第一個事務兩次讀取的數據可能不太一樣。這就發生了在一個事務內兩次讀到的數據是不一樣的情況,因此稱爲不可重複讀。

時間順序 事務1 事務2
1 開始事務
2 開始事務
3 讀取A值30
4 讀取A值30
5 A=A-10
6 讀取A值20
7 提交事務
8 提交事務

幻讀

幻讀與不可重複讀類似。它發生在一個事務讀取了幾行數據,接着另一個併發事務插入了或者刪除一些數據時。在隨後的查詢中,第一個事務就會發現多了一些原本不存在的數據或者少了一些原本存在的數據,就好像發生了幻覺一樣,所以稱爲幻讀。

不可重複讀和幻讀共同點都是事務在多次讀數據的過程中,有其它事務對這些數據進行了更新。不同點是不可重複讀情況中其它事務是更新了數據的內容,而幻讀是增刪了一些數據。

隔離級別

上文提到數據庫通過併發控制來避免併發事務執行帶來的數據不一致性問題,這裏的併發控制通過隔離級別來實現的。

SQL 標準定義了四個隔離級別:
(1) READ-UNCOMMITTED(讀取未提交): 最低的隔離級別,允許讀取尚未提交的數據變更。事務可以在其它事務更改數據時讀取數據,但不能在其它事務更改數據時也進行數據更改,即多個事務可以寫時讀,但不能同時寫(該隔離級別採用是一級封鎖協議,寫前加X鎖,讀操作不需要加鎖,因此可以寫時讀)。禁止了第一類丟失更新,可能會導致髒讀、幻讀或不可重複讀。

(2)READ-COMMITTED(讀取已提交): 允許讀取併發事務已經提交的數據,不可同時寫,不可寫時讀(該隔離級別採用MVCC和鎖或者二級封鎖協議,二級封鎖協議-寫前加X鎖,事務結束才釋放,讀前加S鎖,讀完就釋放)。可以阻止髒讀和第一類丟失更新,但是幻讀或不可重複讀仍有可能發生。

(3)REPEATABLE-READ(可重複讀): 當有事務正在該行數據進行讀操作時禁止其它事務對該行的寫操作,當有事務對該行數據進行寫操作時,禁止其它一切操作。只能共享讀,不可讀時寫,不可寫時讀寫(通過用MVCC和鎖或者使用三級封鎖協議實現,三級封鎖協議-寫前加X鎖,事務結束才釋放,讀前加S鎖,事務結束就釋放)。可重複讀級別使得對同一字段的多次讀取結果都是一致的,除非數據是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生。

(4)SERIALIZABLE(可串行化): 最高的隔離級別,完全服從ACID的隔離級別。併發事務的執行結果與所有事務依次逐個執行結果相同(事務1和事務2併發執行結果與先執行事務1再執行事務2或者先執行事務2再執行事務1的結果相同),事務之間完全不可能相互干擾,該級別可以防止髒讀、不可重複讀以及幻讀。
在這裏插入圖片描述
隔離級別越高,越能保證數據的完整性和一致性,但是對併發性能的影響也就越大。大多數數據庫的默認級別就是Read committed,比如Sql Server 、Oracle。它能夠避免髒讀取,而且具有較好的併發性能,儘管它會導致不可重複讀、幻讀和第二類丟失更新這些併發問題,但可在應用程序中使用鎖來控制。而Mysql的隔離級別是REPEATABLE-READ(可重複讀),它通過與Next-Key Lock 行級鎖鎖算法的配合可以達到SERIALIZABLE(可串行化)級別的效果,可以禁止幻讀。InnoDB使用MVCC來非阻塞的解決了讀寫之間的衝突

拓展-爲什麼REPEATABLE-READ隔離級別可以禁止第二類更新丟失問題,而READ-COMMITTED隔離級別不行。

關鍵點在於REPEATABLE-READ隔離級別使用的是三級封鎖協議,而READ-COMMITTED隔離級別使用的是二級封鎖協議。三級封鎖協議與二級封鎖協議的區別就在於三級封鎖協議在讀數據時需要加S鎖,直至事務結束才釋放S鎖,而二級封鎖協議在讀數據時雖然也需要加S鎖,但讀完數據就可以釋放S鎖。
如下表這個例子,若使用二級封鎖協議:事務1讀取A值之前加S鎖,讀完就釋放,此時事務2也是讀取A值之前加S鎖,讀完就釋放。因此最終事務2的更新還是會覆蓋事務1。而使用三級封鎖協議:事務1讀取A值之前加S鎖,讀取完之後不會釋放,本來是要等事務結束纔會釋放,但由於還需要再次對該行的A值進行更新,S鎖升級爲X鎖,因此一直到事務1結束,事務2都沒有機會讀取或者更新數據,自然不會發生第二類更新丟失。

時間順序 事務1 事務2
1 開始事務
2 開始事務
3 讀取A值30
4 讀取A值30
5 A=A+10
6 提交事務
7 A=A-20
8 提交事務
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章