初識事務,事務隔離級別,事務傳播行為


本篇文章會介紹以下幾個概念:事務,事務隔離級別,spring事務的傳播模式。在介紹事務時會引出原子性的概念,在介紹事務隔離級別的時候會引出臟讀和幻讀的概念。

事務

什么是事務?

事務最開始是數據庫中的概念,它把一系列的操作統一為一個整體,這一系列的操作要么同時成功,要么同時失敗。一個事務基本的操作是:

  1. 開啟事務
  2. 如果發生了錯誤,進行回滾
  3. 如果沒有發生錯誤,則提交事務

為什么要有事務?

在我們處理簡單業務的時候,比如說一條插入數據的操作,只會得到兩個結果,要么插入成功,要么插入失敗,這對應到代碼邏輯上是很簡單的。
我們稱這樣的操作具有原子性。

但是我們的業務往往不會只有插入一條數據那么簡單,可能用戶點擊一個按鈕后,我們需要插入一條數據和刪除一條數據。由於每個操作都有可能成功和失敗,這個時候我們就有了2^2=4種情況,這下編程起來就麻煩了。

為了方便編程(也為了符合實際業務邏輯),我們引入開頭所述的事務機制,把兩個操作放在一個事務里面,使這兩個操作具備原子性,這樣一來業務處理起來就方便多了。

事務隔離級別

上面我們談到了操作數據庫的時候會使用到事務,接下來引入的問題就是:在數據庫中,難免會出現多個事務同時操作數據的情況,這時數據庫設置的事務隔離級別不同,會出現不同的數據操作結果,經典的臟讀與幻讀也誕生於此。

首先給出並發訪問時,不同事務隔離級別下的情況表:

事務隔離級別 臟讀 不可重復讀 幻讀
read uncommitted 讀未提交
read committed 讀提交 不會
repeatable read 可重復讀 不會 不會
serializable 串行化 不會 不會 不會

可以看到事務隔離級別越高,產生的問題越少,但是相應的性能是會降低的,下面通過幾個例子分別闡述不同事務隔離級別下發生的問題:

讀未提交

當事務隔離設置為讀未提交時,最容易產生的問題是臟讀。讀未提交指的是當前事務可以讀到其他事務未提交的數據:假設一位父親給他的兒子打生活費,本來一個月2000但是不小心手抖多打了1000變成3000,這時兒子去商店消費,查詢余額的時候就多了3000。由於父親的事務還沒提交,便立馬回滾事務,重新打過去2000生活費。

這個時候兒子讀到的余額就是臟數據,產生的原因是讀取了其他事務未提交的數據。

讀提交

只要將事務隔離級別設置為讀提交就能解決上面的臟讀問題,他能保證當前事務只能讀到其他事務已經提交的數據。但是讀提交會面臨一個新問題:不可重復讀。

比如說兒子拿着卡到商店消費,買單的時候(開啟當前事務),系統檢測到卡里只有500元。這個時候父親給兒子轉了2000塊生活費,當兒子准備扣費的時候再查詢余額發現變成了2500元(這個查詢發生在父親的轉賬事務提交之后)

在同一個事務中,兒子的余額在不同的時候讀取的值不一樣,這就是不可重復讀問題。想要解決這個問題,需要把事務隔離級別設置為可重復讀

可重復讀

在可重復讀的情況下,當前事務會禁止其他事務對正在操作的數據進行更新,這樣一來,父親轉賬的事務就要等到兒子賬號扣費結束后才能進行,從此解決了不可重復讀問題。

但是可重復讀級別下還可能發生一個問題叫幻讀,舉例如下:兒子今天在外消費了1000元,父親查看兒子一天的消費記錄(開啟事務),發現一共是1000元。這個時候兒子又消費了1000元(父親的事務仍在進行中),接着父親打印兒子今天的消費記錄,發現莫名其妙地變成了2000元,多了一條消費記錄。

像這種當前事務在操作的過程中,由於別的事務增加或刪除數據,導致當前事務操作的數據突然變多或變少的情況,就叫幻讀。想要解決幻讀,需要把事務隔離級別升級為串行化

串行化

當事務隔離級別為串行化時,所有事務都是串行執行的,對應上面的例子:父親在查看當天消費記錄時,兒子是不能消費的。這么一來事務並發帶來的問題都能解決,但是效率很低。

擴展

在常用的數據庫中Orcale默認的事務隔離級別是讀提交,而Mysql默認的是可重復讀。在Mysql的InnoDB引擎中,雖然事務隔離級別是可重復讀,但是也可以解決幻讀問題,背后的原理是在數據行之間添加間隙鎖,防止數據的插入與刪除。

具體選擇那一種事務隔離級別,要看具體的業務需要

事務傳播行為

事務的傳播行為從字面上也是挺好理解的:想要發生傳播就一定要有兩個以上的物體,而這里指的是兩個方法都要在事務中進行,當一個事務方法A調用另一個事務方法B時,另一個事務方法B該如何運行。

Spring一共定義了7種事務傳播行為(事務方法B該如何運行):

傳播行為 含義
PROPAGATION_REQUIRED 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中(這是最常見的選擇,也是spring的默認事務傳播行為)
PROPAGATION_SUPPORTS 支持當前事務,如果當前沒有事務,就以非事務方式執行
PROPAGATION_MANDATORY 使用當前的事務,如果當前沒有事務,就拋出異常
PROPAGATION_REQUIRES_NEW 新建事務,如果當前存在事務,把當前事務掛起
PROPAGATION_NOT_SUPPORTED 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起
PROPAGATION_NEVER 以非事務方式執行,如果當前存在事務,則拋出異常
PROPAGATION_NESTED 如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作

平時我們最常用的是 PROPAGATION_REQUIRED,這也是spring的默認事務傳播行為,理解了它就能按理推導其他的事務傳播行為。

比如說當前我們有如下代碼:

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
     methodB();
    // do something
}
 
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // do something
}
  1. 假設我們執行methodA,由於當前還沒有事務,於是就新創建一個事務。
  2. 當在methodA中調用methodB的時候,由於methodA已經存在於事務中,於是methodB便無需新創建一個事務,直接加入到methodA的事務中即可。

總結

  1. 事務能夠讓一系列不同的操作具有原子性。(當然事務具備ACID四大特性,本文在初步介紹時強調的是原子性)
  2. 事務隔離級別定義了事務並發操作時的訪問規則。
  3. 事務傳播行為定義了事務方法在執行時該怎么運用事務。

參考文章:
事務隔離級別:https://www.cnblogs.com/ubuntu1/p/8999403.html
事務傳播行為:https://blog.csdn.net/weixin_39625809/article/details/80707695
事務傳播行為:https://www.cnblogs.com/softidea/p/5962612.html


免責聲明!

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



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