數據庫—事務—隔離級別


隔離級別

數據庫事務的四個基本特征(ACID)

  1. 原子性(Atomic):事務中包含的操作被看做一個整體的業務單元,這個業務單元中的操作,要么全部成功,要么全部失敗。

  2. 一致性(Consisitency):事務在完成時,必須使所有的數據都保持一致狀態,在數據庫中所有的修改都基於事務,保證了數據的完整性。例,A賬戶有一千元,B賬戶有一千元,A+B = 2000元, A向B轉賬100元,此時A有900元,B有1100元,A+B依然是2000元。

  3. 隔離性(Isolation) :當多個線程訪問同一數據,此時數據庫同樣的數據就會在各個不同的事務中訪問,這樣會產生丟失更新。例,事務A讀取了事務B尚未提交的數據。為了壓制丟失更新的產生,數據庫定義了隔離級別的概念。

  4. 持久性(Durability):事務結束后,所有的數據都會固化到一個地方,如保存到磁盤當中,即時斷電重啟也可以提供給應用程序訪問。

丟失更新類型

這四個特性,除了隔離性都比較好理解。我們再舉例說明下,在多個事務同時操作數據的情況下,會引發丟失更新的場景。例如,電商有一種商品,在瘋狂搶購,此時會出現多個事務同時訪問商品庫存的情景,這樣就會產生丟失更新。一般而言,存在兩種類型的丟失更新。

假設某種商品A庫存數量為100,搶購時,每個用戶僅允許搶購一件商品,name在搶購過程中就可能出現如下場景:

可以看到,T5時刻事務1回滾,此時庫存從99變為了100,事務2 的結果就丟失了。類似的,對於這樣的一個事務回滾另外的一個事務提交引發的數據不一致的情況,稱為第一類丟失更新 。但是,目前大部分數據庫已經克服了第一類丟失更新的問題。

如果多個事務並發提交?

T5時刻提交的事務,以為在事務1中,無法感知事務2的操作,此時事務1不知道事務2 已經修改過了數據,從而產生了錯誤的結果,T5時刻提交事務1的事務,就會引發事務2提交結果的丟失,我們把這樣的多個事務提交引發的丟失更新,稱之為第二類丟失更新。

因隔離級別引發的問題

1.臟讀

指一個事務讀取了另外一個事務未提交的數據。

2.不可重復讀

指在一個事務內讀取表中的某一行數據,多次讀取結果不同。

不可重復讀和臟讀的區別是,臟讀是讀取前一事務未提交的臟數據,不可重復度是重新讀取了前一事務已提交的事務

3. 幻讀

指在一個事務內讀取到了別的事務插入的數據,導致前后讀取不一致

隔離級別類型

為了壓制丟失更新,數據庫標准提出了四類隔離級別,在不同的程度上壓制丟失更新,這四類隔離級別分別是:

  1. 未提交讀
  2. 讀寫提交
  3. 可重復度
  4. 串行化

也許我們會想到,既然是為了壓制丟失更新,直接壓制就好,為什么還要設置級別呢?

數據庫現有技術完全可以避免丟失更新,但是這樣做的代價就是付出鎖的代價。但是在實際開發中,我們不僅僅需要考慮保證數據的一致性問題,也想考慮性能的問題。試想,在調用過程中使用過多的鎖,一旦出現商品搶購的場景,必然會導致大量的線程被掛起和恢復,因為使用了鎖之后,一個時刻只能有一個線程訪問數據,這樣整個系統就會十分緩慢,當系統被數萬用戶同時訪問,過多的鎖就會引發宕機,大部分的線程用戶被掛起,等待持有鎖實物的完成,這樣用戶體驗會十分糟糕。因此數據庫規范就為中和一致性和性能的問題,提出了四種隔離級別來在不同程度上壓制丟失更新。

1.未提交讀

未提交讀(read uncommitted)是最低的隔離界別,其含義是允許一個事務讀取到另一個事務未提交的數據。未提交讀的有點在於並發能力高,適合對數據一致性沒有要求而追求高並發的場景,但他最大的壞處就是會出現臟讀。

在T3時刻,因為采用未提交讀,所以事務2可以讀取到事務1俄日提交的庫存數據為1,當事務2扣減庫存后並提交了事務則庫存為0,然后事務1在T5時刻回滾事務,因為第一類丟失更新已被客服,所以庫存不會回滾到2,最后結果變成了0,這樣就出現了錯誤。

2.讀寫提交

讀寫提交(read committed)隔離級別,是指一個事務只能讀取到另外一個事務已經提交的數據,不能讀取到未提交的數據。

在T3時刻,由於采用了讀寫提交的隔離級別,因此事務2不能讀取到事務1中未提交的庫存1,然后事務2提交事務,則庫存在T4時刻就變為了1。T5時刻,事務1回滾,因為第一類丟失更新已經克服,所以結果庫存為1,這是一個正確的結果。但是讀寫提交也會產生下面的問題。

在T3時刻事務2讀取庫存的時候,由於事務1沒有提交,所以事務2讀到的庫存為1,此時事務2認為當前可扣減庫存;在T4時刻,事務1已經提交事務,所以在T5時刻,它扣減庫存的時候發現庫存為0,扣減失敗。此時庫存對於事務2來說是一個可變化的值,這樣的現象稱為不可重復讀,這就是讀寫提交的一個不足,為了克服這個不足,數據庫隔離級別還提出了可重復讀的隔離級別,他能夠消除不可重讀的問題。

3.可重復讀

可重復讀的目標是克服讀寫提交中出現的不可重復讀的現象,因為在讀寫提交的時候,可能會出現一些值的變化,影響當前事務的執行,如上述的庫存是個變化的值,這個時候數據庫提出了可重復讀的隔離級別。

事務2在T3時刻嘗試讀取庫存,但是此時這個庫存已經被事務1事先讀取,所以此時數據庫就阻塞事務2的讀取,直至事務1提交,事務2才能讀取庫存的值。此時已經是T5時刻,而讀取到的值為0,這是就已經無法扣減了,顯然在讀寫提交中出現的不可重復讀的場景被消除了。但這樣也會引發新的問題,就是幻讀。假設現在商品交易正在進行中,而后台也有人也在進行查詢分析和打印的業務

這便是幻讀現象。首先這里的筆數不是數據庫存儲的值,而是一個統計值,商品庫存則是數據庫存儲的值。幻讀不是針對一條數據庫記錄而言,而是多條記錄,例如,這51筆交易筆數就是多條數據庫記錄統計出來的。而可重復讀是針對數據庫的單一條記錄,例如,商品的庫存是以數據庫里面的一條記錄存儲的,它可以產生可重復讀,而不能產生幻讀。

4.串行化

串行化是數據庫最高的隔離級別,他會要求所有的sql按照順序執行,這樣就可以克服上述隔離級別出現的各種問題,所以它能夠完全保證數據的一致性。

5.使用合理的隔離級別

隔離級別越高,性能越差,所以再選擇隔離級別的時候,要根據業務場景進行考慮選擇。例如,一個高並發搶購的場景,如果采用穿行胡隔離級別,能夠有效的避免數據的不一致性問題,但會導致並發的各個線程掛起,因為只有一個線程可以操作數據,這樣就會導致大量的線程掛起和恢復,導致系統緩慢。后續的用戶要得到系統的相應就需要等待很長的時間,從而影響了用戶的體驗。

所以在現實開發而言,選擇隔離級別會以讀寫提交為主,它能夠防止臟讀,而不能避免不可重復讀和幻讀。

對於隔離級別,不同的數據庫支持也是不同的,oracle只能支持讀寫提交和串行化,而mysql能夠支持四種,對於oracle默認的隔離級別為讀寫提交,mysql則是可重復讀。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM