數據庫的隔離及事務傳播屬性

一、數據庫ACID特性

事務(Transaction)及其ACID屬性

事務是由一組SQL語句組成的邏輯處理單元,事務具有以下4個屬性,通常簡稱爲事務的ACID屬性:

1.1 原子性(Atomicity)

  • 事務是一個原子操作單元,其對數據的修改,要麼全都執行,要麼全都不執行。

1.2 一致性(Consistent)

  • 在事務開始和完成時,數據都必須保持一致狀態。這意味着所有相關的數據規則都必須應用於事務的修改,以保持數據的完整性;事務結束時,所有的內部數據結構(如B樹索引或雙向鏈表)也都必須是正確的。

1.3 隔離性(Isoation)

  • 數據庫系統提供一定的隔離機制,保證事務在不受外部併發操作影響的“獨立”環境執行。這意味着事務處理過程中的中間狀態對外部是不可見的,反之亦然。

1.4 持久性(Durabe)

  • 事務完成之後,它對於數據的修改是永久性的,即使出現系統故障也能夠保持。

二、隔離級別

2.1 數據庫會發生的問題

2.1.1 髒讀-一個事務讀取到另一事務未提交的更新新據。

  • 對於兩個事物 T1, T2, T1 讀取了已經被 T2 更新但還沒有被提交的字段. 之後, 若 T2 回滾, T1讀取的內容就是臨時且無效的.

2.1.2 不可重複讀-同一事務中,多次讀取同一數據返回的結果有所不同(針對的update操作)

  • 換句話說就是,後續讀取可以讀到另一事務已提交的更新數據。相反,“可重複讀”在同一事務中多次讀取數據時,能夠保證所讀數據一樣,也就是,後續讀取不能讀到另一事務已提交的更新數據。
  • 對於兩個事物 T1, T2, T1 讀取了一個字段, 然後 T2 更新了該字段. 之後, T1再次讀取同一個字段, 值就不同了

2.1.3 幻讀-一個事務讀取到另一事務已提交的insert數據(針對的insert操作)

  • 對於兩個事物 T1, T2, T1 從一個表中讀取了一個字段, 然後 T2 在該表中插入了一些新的行. 之後, 如果 T1 再次讀取同一個表, 就會多出幾行.

2.2 數據庫隔離性

數據庫事務的隔離性: 數據庫系統必須具有隔離併發運行各個事務的能力, 使它們不會相互影響, 避免各種併發問題.
一個事務與其他事務隔離的程度稱爲隔離級別. 數據庫規定了多種事務隔離級別, 不同隔離級別對應不同的干擾程度, 隔離級別越高, 數據一致性就越好, 但併發性越弱

數據庫提供了4中隔離級別
髒讀 不可重複讀 幻讀
Read uncommitted
Read committed ×
Repeatable read × ×
Serializable × ×
  • Oracle 支持的 2 種事務隔離級別:READ COMMITED, SERIALIZABLE. Oracle 默認的事務隔離級別爲: READ COMMITED
  • Mysql 支持 4 中事務隔離級別. Mysql 默認的事務隔離級別爲: REPEATABLE READ

  • 注意:我們討論隔離級別的場景,主要是在多個事務併發的情況下

2.2.1 Read uncommitted 讀未提交

公司發工資了,領導把5000元打到singo的賬號上,但是該事務並未提交,而singo正好去查看賬戶,發現工資已經到賬,是5000元整,非常高興。可是不幸的是,領導發現發給singo的工資金額不對,是2000元,於是迅速回滾了事務,修改金額後,將事務提交,最後singo實際的工資只有2000元,singo空歡喜一場。

出現上述情況,即我們所說的髒讀,兩個併發的事務,“事務A:領導給singo發工資”、“事務B:singo查詢工資賬戶”,事務B讀取了事務A尚未提交的數據。

當隔離級別設置爲Read uncommitted時,就可能出現髒讀,如何避免髒讀,請看下一個隔離級別。

2.2.2 Read committed 讀提交

singo拿着工資卡去消費,系統讀取到卡里確實有2000元,而此時她的老婆也正好在網上轉賬,把singo工資卡的2000元轉到另一賬戶,並在singo之前提交了事務,當singo扣款時,系統檢查到singo的工資卡已經沒有錢,扣款失敗,singo十分納悶,明明卡里有錢,爲何……

出現上述情況,即我們所說的不可重複讀,兩個併發的事務,“事務A:singo消費”、“事務B:singo的老婆網上轉賬”,事務A事先讀取了數據,事務B緊接了更新了數據,並提交了事務,而事務A再次讀取該數據時,數據已經發生了改變。

  • 當隔離級別設置爲Read committed時,避免了髒讀,但是可能會造成不可重複讀。

  • 大多數數據庫的默認級別就是Read committed,比如Sql Server , Oracle,postgresql

2.2.3 Repeatable read 重複讀

當隔離級別設置爲Repeatable read時,可以避免不可重複讀。當singo拿着工資卡去消費時,一旦系統開始讀取工資卡信息(即事務開始),singo的老婆就不可能對該記錄進行修改,也就是singo的老婆不能在此時轉賬。

  • 雖然Repeatable read避免了不可重複讀,但還有可能出現幻讀。

singo的老婆工作在銀行部門,她時常通過銀行內部系統查看singo的信用卡消費記錄。有一天,她正在查詢到singo當月信用卡的總消費金額(select sum(amount) from transaction where month = 本月)爲80元,而singo此時正好在外面胡吃海塞後在收銀臺買單,消費1000元,即新增了一條1000元的消費記錄(insert transaction … ),並提交了事務,隨後singo的老婆將singo當月信用卡消費的明細打印到A4紙上,卻發現消費總額爲1080元,singo的老婆很詫異,以爲出現了幻覺,幻讀就這樣產生了。

  • 注:Mysql的默認隔離級別就是Repeatable read。(使用MVCC實現)

2.2.4 Serializable 序列化

Serializable是最高的事務隔離級別,同時代價也花費最高,性能很低,一般很少使用,在該級別下,事務順序執行,不僅可以避免髒讀、不可重複讀,還避免了幻像讀。

三、事務

3.1 事務的兩種狀態

  • 回滾
    Spring容器默認對運行期例外,進行事務回滾 用戶例外(checked exception)事務不會回滾

  • 提交

3.2 事務傳播屬性

  • 傳播行爲決定了事務上下文是否共享,子子事務若共享父事務的狀態,那麼對子事務的操作,就會反映到父事務(核心)
  • 事務的傳播行爲體現了其原子性
  • 事務傳播屬性註解必須應用到公有方法

3.2.1 REQUIRED

業務方法需要在一個事務中運行。如果方法運行時,已經處在一個事務中,那麼加入到該事務,否則爲自己創建一個新的事務。

3.2.2 NOT_SUPPORTED

聲明方法不需要事務。如果方法沒有關聯到一個事務,容器不會爲它開啓事務。如果方法在一個事務中被調用,該事務會被掛起,在方法調用結束後,原先的事務便會恢復執行。
應用場景:有數據操作處理(需要事務)+異步調用(不需要事務,掛起)

3.2.3 REQUIRESNEW

屬性表明不管是否存在事務,業務方法總會爲自己發起一個新的事務。如果方法已經運行在一個事務中,則原有事務會被掛起,新的事務會被創建,直到方法執行結束,新事務纔算結束,原先的事務纔會恢復執行。

3.2.4 MANDATORY

該屬性指定業務方法只能在一個已經存在的事務中執行,業務方法不能發起自己的事務。如果業務方法在沒有事務的環境下調用,容器就會拋出例外。

3.2.5 SUPPORTS

這一事務屬性表明,如果業務方法在某個事務範圍內被調用,則方法成爲該事務的一部分。如果業務方法在事務範圍外被調用,則方法在沒有事務的環境下執行。

3.2.6 Never

指定業務方法絕對不能在事務範圍內執行。如果業務方法在某個事務中執行,容器會拋出例外,只有業務方法沒有關聯到任何事務,才能正常執行。
例:應用於報表統計程序

3.2.7 NESTED

如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, 則按REQUIRED屬性執行.它使用了一個單獨的事務, 這個事務擁有多個可以回滾的保存點。內部事務的回滾不會對外部事務造成影響。它只對DataSourceTransactionManager事務管理器起效

事務嵌套,子事務的成功與失敗不影響主事務,主事務的成功失敗影響子事務

3.3 事物超時設置

@Transactional(timeout=30) //默認是30秒

3.4 事務隔離級別配置

事務隔離級別:
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
讀取未提交數據(會出現髒讀, 不可重複讀) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)
讀取已提交數據(會出現不可重複讀和幻讀)
@Transactional(isolation = Isolation.REPEATABLE_READ)
可重複讀(會出現幻讀)
@Transactional(isolation = Isolation.SERIALIZABLE)
串行化

3.5 @Transactional註解中常用參數說明

參 數 名 稱 功 能 描 述
readOnly 該屬性用於設置當前事務是否爲只讀事務,設置爲true表示只讀,false則表示可讀寫,默認值爲false。例如:@Transactional(readOnly=true)
rollbackFor 該屬性用於設置需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,則進行事務回滾。例如:指定單一異常類:@Transactional(rollbackFor=RuntimeException.class)指定多個異常類:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
rollbackForClassName 該屬性用於設置需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,則進行事務回滾。例如:指定單一異常類名稱:@Transactional(rollbackForClassName=”RuntimeException”)指定多個異常類名稱:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”})
noRollbackFor 該屬性用於設置不需要進行回滾的異常類數組,當方法中拋出指定異常數組中的異常時,不進行事務回滾。例如:指定單一異常類:@Transactional(noRollbackFor=RuntimeException.class)指定多個異常類:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
noRollbackForClassName 該屬性用於設置不需要進行回滾的異常類名稱數組,當方法中拋出指定異常名稱數組中的異常時,不進行事務回滾。例如:指定單一異常類名稱:@Transactional(noRollbackForClassName=”RuntimeException”)指定多個異常類名稱:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”})
propagation 該屬性用於設置事務的傳播行爲
isolation 該屬性用於設置底層數據庫的事務隔離級別,事務隔離級別用於處理多事務併發的情況,通常使用數據庫的默認隔離級別即可,基本不需要進行設置
timeout 該屬性用於設置事務的超時秒數,默認值爲-1表示永不超時

注意的幾點
1 @Transactional 只能被應用到public方法上, 對於其它非public的方法,如果標記了@Transactional也不會報錯,但方法沒有事務功能.

2用 spring 事務管理器,由spring來負責數據庫的打開,提交,回滾.默認遇到運行期例外(throw new RuntimeException(“註釋”);)會回滾,即遇到不受檢查(unchecked)的例外時回滾;而遇到需要捕獲的例外(throw new Exception(“註釋”);)不會回滾,即遇到受檢查的例外(就是非運行時拋出的異常,編譯器會檢查到的異常叫受檢查例外或說受檢查異常)時,需我們指定方式來讓事務回滾 要想所有異常都回滾,要加上 @Transactional( rollbackFor={Exception.class,其它異常}) .如果讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class)
如下:
@Transactional(rollbackFor=Exception.class) //指定回滾,遇到異常Exception時回滾
public void methodName() {
throw new Exception(“註釋”);

}
@Transactional(noRollbackFor=Exception.class)//指定不回滾,遇到運行期例外(throw new RuntimeException(“註釋”);)會回滾
public ItimDaoImpl getItemDaoImpl() {
throw new RuntimeException(“註釋”);
}

3、@Transactional 註解應該只被應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 註解,它也不會報錯, 但是這個被註解的方法將不會展示已配置的事務設置。

4、@Transactional 註解可以被應用於接口定義和接口方法、類定義和類的 public 方法上。然而,請注意僅僅 @Transactional 註解的出現不足於開啓事務行爲,它僅僅 是一種元數據,能夠被可以識別 @Transactional 註解和上述的配置適當的具有事務行爲的beans所使用。上面的例子中,其實正是 元素的出現 開啓 了事務行爲。

5、Spring團隊的建議是你在具體的類(或類的方法)上使用 @Transactional 註解,而不要使用在類所要實現的任何接口上。你當然可以在接口上使用 @Transactional 註解,但是這將只能當你設置了基於接口的代理時它才生效。因爲註解是 不能繼承 的,這就意味着如果你正在使用基於類的代理時,那麼事務的設置將不能被基於類的代理所識別,而且對象也將不會被事務代理所包裝(將被確認爲嚴重的)。因 此,請接受Spring團隊的建議並且在具體的類上使用 @Transactional 註解。

四、實際場景中的七大事務傳播行爲的使用

4.1 在一個話費充值業務處理邏輯中,有如下圖所示操作:

  • 業務需要扣款操作和創建訂單操作同成功或者失敗,因此,charger()和order()的事務不能相互獨立,需要包含在chargeHandle()的事務中;
  • 通過以上需求,可以給charge()和order()的事務傳播行爲定義成:PROPAGATION_MANDATORY
    只要charge()或者order()拋出異常整個chargeHandle()都一起回滾,即使chargeHandle()捕獲異常也沒用,不允許提交事務

4.2 如果業務需求沒接受到一次請求到要記錄日誌到數據庫

如下圖:


因爲log()的操作不管扣款和創建訂單成功與否都要生成日誌,並且日誌的操作成功與否不影響充值處理,所以log()方法的事務傳播行爲可以定義爲:PROPAGATION_REQUIRES_NEW.

4.3 在訂單的售後處理中,更新完訂單金額後,需要自動統計銷售報表

如下圖所示


根據業務可知,售後是已經處理完訂單的充值請求後的功能,是對訂單的後續管理,統計報表report()方法耗時較長,因此,我們需要設置report()的事務傳播行爲爲:PROPAGATION_NEVER,表示不適合在有事務的操作中調用,因爲report()太耗時。

4.4 在銀行新增銀行卡業務中,需要執行兩個操作

一個是保存銀行卡信息,一個是登記新創建的銀行卡信息,其中登記銀行卡信息成功與否不影響銀行卡的創建。

  • 由以上需求,我們可知對於regster()方法的事務傳播行爲,可以設置爲PROPAGATION_NESTED

  • action()事務的回滾,regster()保存的信息就沒意義,也就需要跟着回滾,而regster()的回滾不影響action()事務;

  • insert()的事務傳播行爲可以設置爲PROPAGATION_REQUIRED, PROPAGATION_MANDATORY,即insert()回滾事務,action()的事務必須跟着回滾。

發佈了105 篇原創文章 · 獲贊 63 · 訪問量 160萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章