1. 程式人生 > >[spring]事務傳播級別隔離級別以及高併發下的應用經驗

[spring]事務傳播級別隔離級別以及高併發下的應用經驗

事務是邏輯處理原子性的保證手段,通過使用事務控制,可以極大的避免出現邏輯處理失敗導致的髒資料等問題。

事務最重要的兩個特性,是事務的傳播級別和資料隔離級別。傳播級別定義的是事務的控制範圍,事務隔離級別定義的是事務在資料庫讀寫方面的控制範圍。


以下是事務的7種傳播級別:


1) PROPAGATION_REQUIRED ,預設的spring事務傳播級別,使用該級別的特點是,如果上下文中已經存在事務,那麼就加入到事務中執行,如果當前上下文中不存在事務,則新建事務執行。所以這個級別通常能滿足處理大多數的業務場景。


2)PROPAGATION_SUPPORTS ,從字面意思就知道,supports,支援,該傳播級別的特點是,如果上下文存在事務,則支援事務加入事務,如果沒有事務,則使用非事務的方式執行。所以說,並非所有的包在transactionTemplate.execute中的程式碼都會有事務支援。這個通常是用來處理那些並非原子性的非核心業務邏輯操作。應用場景較少。


3)PROPAGATION_MANDATORY

 , 該級別的事務要求上下文中必須要存在事務,否則就會丟擲異常!配置該方式的傳播級別是有效的控制上下文呼叫程式碼遺漏新增事務控制的保證手段。比如一段程式碼不能單獨被呼叫執行,但是一旦被呼叫,就必須有事務包含的情況,就可以使用這個傳播級別。


4)PROPAGATION_REQUIRES_NEW ,從字面即可知道,new,每次都要一個新事務,該傳播級別的特點是,每次都會新建一個事務,並且同時將上下文中的事務掛起,執行當前新建事務完成以後,上下文事務恢復再執行。


問題 :如果其中一個子事務回滾了,父事務是否回滾?答案是不會,因為子事務是新建事務,父事務已經被掛起,兩者不會受到影響。

再問:如果父事務回滾了,子事務是否回滾?答案是不會,同樣的理由。但是可以手動控制一旦子事務回滾,父事務也回滾。


這是一個很有用的傳播級別,舉一個應用場景:現在有一個傳送100個紅包的操作,在傳送之前,要做一些系統的初始化、驗證、資料記錄操作,然後傳送100封紅包,然後再記錄傳送日誌,傳送日誌要求100%的準確,如果日誌不準確,那麼整個父事務邏輯需要回滾。


怎麼處理整個業務需求呢?就是通過這個PROPAGATION_REQUIRES_NEW 級別的事務傳播控制就可以完成。傳送紅包的子事務不會直接影響到父事務的提交和回滾。


5)PROPAGATION_NOT_SUPPORTED 
,這個也可以從字面得知,not supported ,不支援,當前級別的特點就是上下文中存在事務,則掛起事務,執行當前邏輯,結束後恢復上下文的事務。


這個級別有什麼好處?可以幫助你將事務極可能的縮小。我們知道一個事務越大,它存在的風險也就越多。所以在處理事務的過程中,要保證儘可能的縮小範圍。比如一段程式碼,是每次邏輯操作都必須呼叫的,比如迴圈1000次的某個非核心業務邏輯操作。這樣的程式碼如果包在事務中,勢必造成事務太大,導致出現一些難以考慮周全的異常情況。所以這個事務這個級別的傳播級別就派上用場了。用當前級別的事務模板抱起來就可以了。


6)PROPAGATION_NEVER ,該事務更嚴格,上面一個事務傳播級別只是不支援而已,有事務就掛起,而PROPAGATION_NEVER傳播級別要求上下文中不能存在事務,一旦有事務,就丟擲runtime異常,強制停止執行!這個級別上輩子跟事務有仇。


7)PROPAGATION_NESTED 
,字面也可知道,nested,巢狀級別事務。該傳播級別特徵是,如果上下文中存在事務,則巢狀事務執行,如果不存在事務,則新建事務。

那麼什麼是巢狀事務呢?很多人都不理解,我看過一些部落格,都是有些理解偏差。

巢狀是子事務套在父事務中執行,子事務是父事務的一部分,在進入子事務之前,父事務建立一個回滾點,叫save point,然後執行子事務,這個子事務的執行也算是父事務的一部分,然後子事務執行結束,父事務繼續執行。重點就在於那個save point。看幾個問題就明瞭了:

如果子事務回滾,會發生什麼? 

父事務會回滾到進入子事務前建立的save point,然後嘗試其他的事務或者其他的業務邏輯,父事務之前的操作不會受到影響,更不會自動回滾。


如果父事務回滾,會發生什麼? 

父事務回滾,子事務也會跟著回滾!為什麼呢,因為父事務結束之前,子事務是不會提交的,我們說子事務是父事務的一部分,正是這個道理。那麼:


事務的提交,是什麼情況? 

是父事務先提交,然後子事務提交,還是子事務先提交,父事務再提交?答案是第二種情況,還是那句話,子事務是父事務的一部分,由父事務統一提交。


現在你再體會一下這個”巢狀“,是不是有那麼點意思?


以上是事務的7個傳播級別,在日常應用中,通常可以滿足各種業務需求,但是除了傳播級別,在讀取資料庫的過程中,如果兩個事務併發執行,那麼彼此之間的資料是如何影響的呢?

這就需要了解一下事務的另一個特性:資料隔離級別

資料隔離級別分為不同的四種:


1、Serializable :最嚴格的級別,事務序列執行,資源消耗最大;

2、REPEATABLE READ :保證了一個事務不會修改已經由另一個事務讀取但未提交(回滾)的資料。避免了“髒讀取”和“不可重複讀取”的情況,但是帶來了更多的效能損失。

3、READ COMMITTED :大多數主流資料庫的預設事務等級,保證了一個事務不會讀到另一個並行事務已修改但未提交的資料,避免了“髒讀取”。該級別適用於大多數系統。

4、Read Uncommitted :保證了讀取過程中不會讀取到非法資料。
 
上面的解釋其實每個定義都有一些拗口,其中涉及到幾個術語:髒讀、不可重複讀、幻讀。
這裡解釋一下:
 
髒讀 :所謂的髒讀,其實就是讀到了別的事務回滾前的髒資料。比如事務B執行過程中修改了資料X,在未提交前,事務A讀取了X,而事務B卻回滾了,這樣事務A就形成了髒讀。
 
不可重複讀 :不可重複讀字面含義已經很明瞭了,比如事務A首先讀取了一條資料,然後執行邏輯的時候,事務B將這條資料改變了,然後事務A再次讀取的時候,發現數據不匹配了,就是所謂的不可重複讀了。
 
幻讀 :小的時候數手指,第一次數十10個,第二次數是11個,怎麼回事?產生幻覺了?
幻讀也是這樣子,事務A首先根據條件索引得到10條資料,然後事務B改變了資料庫一條資料,導致也符合事務A當時的搜尋條件,這樣事務A再次搜尋發現有11條資料了,就產生了幻讀。
 
一個對照關係表:
                          Dirty reads          non-repeatable reads            phantom reads
Serializable                     不會                   不會                           不會
REPEATABLE READ           不會                   不會                            會
READ COMMITTED            不會                    會                             會
Read Uncommitted            會                     會                             會
 
所以最安全的,是Serializable,但是伴隨而來也是高昂的效能開銷。
另外,事務常用的兩個屬性:readonly和timeout
一個是設定事務為只讀以提升效能。
另一個是設定事務的超時時間,一般用於防止大事務的發生。還是那句話,事務要儘可能的小!

最後引入一個問題:
一個邏輯操作需要檢查的條件有20條,能否為了減小事務而將檢查性的內容放到事務之外呢? 

很多系統都是在DAO的內部開始啟動事務,然後進行操作,最後提交或者回滾。這其中涉及到程式碼設計的問題。小一些的系統可以採用這種方式來做,但是在一些比較大的系統,
邏輯較為複雜的系統中,勢必會將過多的業務邏輯嵌入到DAO中,導致DAO的複用性下降。所以這不是一個好的實踐。

---------------分割PS:csdn的竟然崩潰了,而且無法恢復內容所以這是我第二次重寫這篇文章。。。。


來回答這個問題:能否為了縮小事務,而將一些業務邏輯檢查放到事務外面?答案是:對於核心的業務檢查邏輯,不能放到事務之外,而且必須要作為分散式下的併發控制!
一旦在事務之外做檢查,那麼勢必會造成事務A已經檢查過的資料被事務B所修改,導致事務A徒勞無功而且出現併發問題,直接導致業務控制失敗。
所以,在分散式的高併發環境下,對於核心業務邏輯的檢查,要採用加鎖機制。
比如事務開啟需要讀取一條資料進行驗證,然後邏輯操作中需要對這條資料進行修改,最後提交。
這樣的一個過程,如果讀取並驗證的程式碼放到事務之外,那麼讀取的資料極有可能已經被其他的事務修改,當前事務一旦提交,又會重新覆蓋掉其他事務的資料,導致資料異常。
所以在進入當前事務的時候,必須要將這條資料鎖住,使用for update就是一個很好的在分散式環境下的控制手段。

一種好的實踐方式是使用程式設計式事務而非生命式,尤其是在較為規模的專案中。對於事務的配置,在程式碼量非常大的情況下,將是一種折磨,而且人肉的方式,絕對不能避免這種問題。
將DAO保持針對一張表的最基本操作,然後業務邏輯的處理放入manager和service中進行,同時使用程式設計式事務更精確的控制事務範圍。
特別注意的,對於事務內部一些可能丟擲異常的情況,捕獲要謹慎,不能隨便的catch Exception 導致事務的異常被吃掉而不能正常回滾。

在高併發的分散式系統中,本地事務的處理要極為小心,比如ws超時等問題是否要回滾當前事務或者觸發重試機制,都要考慮特定的業務場景來決定。尤其是具有一定風險的系統。如支付寶。