1. 程式人生 > >初識事務,事務隔離級別,事務傳播行為

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

本篇文章會介紹以下幾個概念:事務,事務隔離級別,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.h