Spring五個事務隔離級別和七個事務傳播行為


來源:https://yq.aliyun.com/articles/48893

 

Spring五個事務隔離級別和七個事務傳播行為

1. 臟讀 :臟讀就是指當一個事務正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務也訪問這個數據,然后使用了這個數據。

2. 不可重復讀 :是指在一個事務內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務也訪問該同一數據。那么,在第一個事務中的兩 次讀數據之間,由於第二個事務的修改,那么第一個事務兩次讀到的的數據可能是不一樣的。這樣就發生了在一個事務內兩次讀到的數據是不一樣的,因此稱為是不 可重復讀。例如,一個編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。原始讀取不可重復。如果 只有在作者全部完成編寫后編輯人員才可以讀取文檔,則可以避免該問題。spacer.gif …數據庫事務和Spring事務是一般面試都會被提到,很多朋友寫慣了代碼,很少花時間去整理歸納這些東西,結果本來會的東西,居然吞吞吐吐答不上來。

下面是我收集到一些關於Spring事務的問題,希望能幫助大家過關。

3. 幻讀 : 是指當事務不是獨立執行時發生的一種現象,例如第一個事務對一個表中的數據進行了修改,這種修改涉及到表中的全部數據行。 同時,第二個事務也修改這個表中的數據,這種修改是向表中插入一行新數據。那么,以后就會發生操作第一個事務的用戶發現表中還有沒有修改的數據行,就好象 發生了幻覺一樣。例如,一個編輯人員更改作者提交的文檔,但當生產部門將其更改內容合並到該文檔的主復本時,發現作者已將未編輯的新材料添加到該文檔中。 如果在編輯人員和生產部門完成對原始文檔的處理之前,任何人都不能將新材料添加到文檔中,則可以避免該問題。

補充 : 基於元數據的 Spring 聲明性事務 :

Isolation 屬性一共支持五種事務設置,具體介紹如下:

<!—->l          <!—->DEFAULT 使用數據庫設置的隔離級別 ( 默認 ) ,由 DBA 默認的設置來決定隔離級別 .

<!—->l          <!—->READ_UNCOMMITTED 會出現臟讀、不可重復讀、幻讀 ( 隔離級別最低,並發性能高 )

<!—->l          <!—->READ_COMMITTED  會出現不可重復讀、幻讀問題(鎖定正在讀取的行)

<!—->l          <!—->REPEATABLE_READ 會出幻讀(鎖定所讀取的所有行)

<!—->l          <!—->SERIALIZABLE 保證所有的情況不會發生(鎖表)

不可重復讀的重點是修改 : 
同樣的條件 ,   你讀取過的數據 ,   再次讀取出來發現值不一樣了 
幻讀的重點在於新增或者刪除 
同樣的條件 ,   第 1 次和第 2 次讀出來的記錄數不一樣

Spring在TransactionDefinition接口中定義這些屬性

在TransactionDefinition接口中定義了五個不同的事務隔離級別

ISOLATION_DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別.另外四個與JDBC的隔離級別相對應 
ISOLATION_READ_UNCOMMITTED 這是事務最低的隔離級別,它充許別外一個事務可以看到這個事務未提交的數據。這種隔離級別會產生臟讀,不可重復讀和幻像讀

ISOLATION_READ_COMMITTED 保證一個事務修改的數據提交后才能被另外一個事務讀取。另外一個事務不能讀取該事務未提交的數據。這種事務隔離級別可以避免臟讀出現,但是可能會出現不可重復讀和幻像讀。

ISOLATION_REPEATABLE_READ 這種事務隔離級別可以防止臟讀,不可重復讀。但是可能出現幻像讀。它除了保證一個事務不能讀取另一個事務未提交的數據外,還保證了避免下面的情況產生(不可重復讀)。

ISOLATION_SERIALIZABLE 這是花費最高代價但是最可靠的事務隔離級別。事務被處理為順序執行。除了防止臟讀,不可重復讀外,還避免了幻像讀。

 

在TransactionDefinition接口中定義了七個事務傳播行為。

PROPAGATION_REQUIRED 如果存在一個事務,則支持當前事務。如果沒有事務則開啟一個新的事務。

PROPAGATION_SUPPORTS 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行。但是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少許不同。

PROPAGATION_MANDATORY 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常。

PROPAGATION_REQUIRES_NEW 總是開啟一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起。

PROPAGATION_NOT_SUPPORTED 總是非事務地執行,並掛起任何存在的事務。

PROPAGATION_NEVER 總是非事務地執行,如果存在一個活動事務,則拋出異常

PROPAGATION_NESTED如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行

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

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

事務的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的復用性下降。所以這不是一個好的實踐。

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

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

Spring配置聲明式事務:

* 配置DataSource
* 配置事務管理器
* 事務的傳播特性
* 那些類那些方法使用事務

Spring配置文件中關於事務配置總是由三個組成部分,分別是DataSource、TransactionManager和代理機制這三部分,無論哪種配置方式,一般變化的只是代理機制這部分。

    DataSource、TransactionManager這兩部分只是會根據數據訪問方式有所變化,比如使用Hibernate進行數據訪問 時,DataSource實際為SessionFactory,TransactionManager的實現為 HibernateTransactionManager。

根據代理機制的不同,Spring事務的配置又有幾種不同的方式:

第一種方式:每個Bean都有一個代理

 第二種方式:所有Bean共享一個代理基類

第三種方式:使用攔截器

第四種方式:使用tx標簽配置的攔截器

第五種方式:全注解

1、spring事務控制放在service層,在service方法中一個方法調用service中的另一個方法,默認開啟幾個事務?

spring的事務傳播方式默認是PROPAGATION_REQUIRED,判斷當前是否已開啟一個新事務,有則加入當前事務,否則新開一個事務(如果沒有就開啟一個新事務),所以答案是開啟了一個事務。

2、spring 什么情況下進行事務回滾?

Spring、EJB的聲明式事務默認情況下都是在拋出unchecked exception后才會觸發事務的回滾

unchecked異常,即運行時異常runntimeException 回滾事務;

checked異常,即Exception可try{}捕獲的不會回滾.當然也可配置spring參數讓其回滾.

spring的事務邊界是在調用業務方法之前開始的,業務方法執行完畢之后來執行commit or rollback(Spring默認取決於是否拋出runtime異常).
如果拋出runtime exception 並在你的業務方法中沒有catch到的話,事務會回滾。
一般不需要在業務方法中catch異常,如果非要catch,在做完你想做的工作后(比如關閉文件等)一定要拋出runtime exception,否則spring會將你的操作commit,這樣就會產生臟數據.所以你的catch代碼是畫蛇添足。


免責聲明!

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



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