一文搞懂什么是事務
目錄- 一文搞懂什么是事務
- 事務概念
- 臟讀、不可重復讀、幻讀
- 臟讀
- 不可重復讀(前后多次讀取,資料內容不一致)
- 幻讀(前后多次讀取,資料總量不一致)
- 資料庫事務的隔離級別
- DEFAULT
- READ UNCOMMITTED(讀未提交)
- READ_COMMITTED (讀提交)
- REPEATABLE_READ (可重復讀)
- SERIALIZABLE (串行化)
- MVCC(多版本并發控制)
- Spring事務傳播行為
- Spring 事務的兩種實作
- 兩種事務管理間的區別
- Spring 編程式事務
- Spring 宣告式事務
- 常用的宣告式事務使用方法
- @Transactional 的作用范圍
- @Transactional 注解中可配置引數
- 示例
- 使用事務時需要注意的點
- 總結
事務概念
我們要理解下事務概念: 什么是事務呢?事務是并發控制的單位,是用戶定義的一個操作序列,有四個特性(ACID):
-
原子性(Atomicity): 事務是資料庫的邏輯作業單位,事務中包括的諸操作要么全做,要么全不做,
-
一致性(Consistency): 事務執行的結果必須是使資料庫從一個一致性狀態變到另一個一致性狀態,一致性與原子性是密切相關的,
-
隔離性(Isolation): 一個事務的執行不能被其他事務干擾,
-
持續性/永久性(Durability): 一個事務一旦提交,它對資料庫中資料的改變就應該是永久性的,
以上是書面解釋,簡單來說就是把你的操作統一化,要么所有操作都成功,要么就都不成功,如果執行中有某一項操作失敗,其之前所有的操作都回滾到未執行這一系列操作之前的狀態,
臟讀、不可重復讀、幻讀
先理解這三種由于并發訪問導致的資料讀取問題,再理解事務隔離級別就簡單多了,
臟讀
A事務讀取B事務尚未提交的資料,此時如果B事務發生錯誤并執行回滾操作,那么A事務讀取到的資料就是臟資料,就好像原本的資料比較干凈、純粹,此時由于B事務更改了它,這個資料變得不再純粹,這個時候A事務立即讀取了這個臟資料,但事務B良心發現,又用回滾把資料恢復成原來干凈、純粹的樣子,而事務A卻什么都不知道,最終結果就是事務A讀取了此次的臟資料,稱為臟讀,
這種情況常發生于轉賬與取款操作中

不可重復讀(前后多次讀取,資料內容不一致)
事務A在執行讀取操作,由整個事務A比較大,前后讀取同一條資料需要經歷很長的時間 ,而在事務A第一次讀取資料,比如此時讀取了小明的年齡為20歲,事務B執行更改操作,將小明的年齡更改為30歲,此時事務A第二次讀取到小明的年齡時,發現其年齡是30歲,和之前的資料不一樣了,也就是資料不重復了,系統不可以讀取到重復的資料,成為不可重復讀,

幻讀(前后多次讀取,資料總量不一致)
事務A在執行讀取操作,需要兩次統計資料的總量,前一次查詢資料總量后,此時事務B執行了新增資料的操作并提交后,這個時候事務A讀取的資料總量和之前統計的不一樣,就像產生了幻覺一樣,平白無故的多了幾條資料,成為幻讀,

小總結:不可重復讀和幻讀到底有什么區別?
(1) 不可重復讀是讀取了其他事務更改的資料,針對update操作
解決:使用行級鎖,鎖定該行,事務A多次讀取操作完成后才釋放該鎖,這個時候才允許其他事務更改剛才的資料,
(2) 幻讀是讀取了其他事務新增的資料,針對insert和delete操作
解決:使用表級鎖,鎖定整張表,事務A多次讀取資料總量之后才釋放該鎖,這個時候才允許其他事務新增資料,
這時候再理解事務隔離級別就簡單多了呢,
資料庫事務的隔離級別
SQL 標準定義的四種隔離級別被 ANSI(美國國家標準學會)和 ISO/IEC(國際標準)采用,每種級別對事務的處理能力會有不同程度的影響,事務是一系列的動作,它們綜合在一起才是一個完整的作業單元,這些動作必須全部完成,如果有一個失敗的話,那么事務就會回滾到最開始的狀態,仿佛什么都沒發生過一樣,
資料庫事務的隔離級別有4個,由低到高依次為Read uncommitted 、Read committed 、Repeatable read 、Serializable ,這四個級別可以逐個解決臟讀 、不可重復讀 、幻讀 這幾類問題,

DEFAULT
默認值,表示使用底層資料庫的默認隔離級別,大部分資料庫為READ_COMMITTED(MySql默認REPEATABLE_READ)
READ UNCOMMITTED(讀未提交)
該隔離級別表示一個事務可以讀取另一個事務修改但還沒有提交的資料,該級別不能防止臟讀和不可重復讀,因此很少使用該隔離級別,
READ_COMMITTED (讀提交)
該隔離級別表示一個事務只能讀取另一個事務已經提交的資料,該級別可以防止臟讀,這也是大多數情況下的推薦值,
REPEATABLE_READ (可重復讀)
該隔離級別表示一個事務在整個程序中可以多次重復執行某個查詢,并且每次回傳的記錄都相同,即使在多次查詢之間有新增的資料滿足該查詢,這些新增的記錄也會被忽略,該級別可以防止臟讀和不可重復讀,
SERIALIZABLE (串行化)
所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止臟讀、不可重復讀以及幻讀,但是這將嚴重影響程式的性能,通常情況下也不會用到該級別, 在該隔離級別下事務都是串行順序執行的,MySQL 資料庫的 InnoDB 引擎會給讀操作隱式加一把讀共享鎖,從而避免了臟讀、不可重讀復讀和幻讀問題,
MVCC(多版本并發控制)
mysql中,默認的事務隔離級別是可重復讀(repeatable-read),為了解決不可重復讀,innodb采用了MVCC(多版本并發控制)來解決這一問題,
MVCC是利用在每條資料后面加了隱藏的兩列(創建版本號和洗掉版本號),每個事務在開始的時候都會有一個遞增的版本號,用來和查詢到的每行記錄的版本號進行比較, MYSQL MVCC
Spring事務傳播行為
先來介紹下Spring事務傳播行為的使用方法:
@Transactional(propagation=Propagation.REQUIRED)
public void test() {
//todo something
}
注解@Transactional
通過使用 propagation 屬性設定,例如:@Transactional(propagation = Propagation.REQUIRED)
它的propagation屬性取值有以下幾種:
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
}
事務傳播行為:
REQUIRED:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務,SUPPORTS:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行,MANDATORY:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出例外,REQUIRES_NEW:創建一個新的事務,如果當前存在事務,則把當前事務掛起,NOT_SUPPORTED:以非事務方式運行,如果當前存在事務,則把當前事務掛起,NEVER:以非事務方式運行,如果當前存在事務,則拋出例外,NESTED:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價于 REQUIRED
Spring 事務的兩種實作
Spring 支持“編程式事務”管理和“宣告式事務”管理兩種方式:
1編程式事務: 編程式事務使用 TransactionTemplate 或者直接使用底層的 PlatformTransactionManager 實作事務, 對于編程式事務 Spring 比較推薦使用 TransactionTemplate 來對事務進行管理,
2宣告式事務: 宣告式事務是建立在 AOP 之上的,其本質是對方法前后進行攔截,然后在目標方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況“提交”或者“回滾”事務,
兩種事務管理間的區別
- 編程式事務允許用戶在代碼中精確定義事務的邊界,
- 宣告式事務有助于用戶將操作與事務規則進行解耦,它是基于 AOP 交由 Spring 容器實作,是開發人員只重點關注業務邏輯實作,
- 編程式事務侵入到了業務代碼里面,但是提供了更加纖細的事務管理,而宣告式事務基于 AOP,所以既能起到事務作用,又可以不影響業務代碼的具體實作,一般而言比較推薦使用宣告式事務,尤其是使用 @Transactional 注解,它能很好地幫助開發者實作事務的同時,也減少代碼開發量,且使代碼看起來更加清爽整潔,
Spring 編程式事務
一般來說編程式事務有兩種方法可以實作:
模板事務的方式(TransactionTemplate)和 平臺事務管理器方式(PlatformTransactionManager)
- 模板事務的方式(TransactionTemplate): 主要是使用 TransactionTemplate 類實作事務,這也是 Spring 官方比較推薦的一種編程式使用方式;
例:
- ① 獲取模板物件 TransactionTemplate;
- ② 選擇事務結果型別;
- ③ 業務資料操作處理;
- ④ 業務執行完成事務提交或者發生例外進行回滾;
其中 TransactionTemplate 的 execute 能接受兩種型別引數執行事務,分別為:
TransactionCallback<Object>(): 執行事務且可以回傳一個值,
TransactionCallbackWithoutResult(): 執行事務沒有回傳值,
下面是使用 TransactionTemplate 的實體:
@Service
public class TransactionExample {
/** 1、獲取 TransactionTemplate 物件 **/
@Autowired
private TransactionTemplate transactionTemplate;
public void addUser() {
// 2、使用 TransactionCallback 或者 TransactionCallbackWithoutResult 執行事務
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
public void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// 3、執行業務代碼(這里進行模擬,執行多個資料庫操作方法)
userMapper.delete(1);
userMapper.delete(2);
} catch (Exception e) {
// 4、發生例外,進行回滾
transactionStatus.setRollbackOnly();
}
}
});
}
}
- 平臺事務管理器方式(PlatformTransactionManager): 這里使用最基本的事務管理局對事務進行管理,借助 Spring 事務的 PlatformTransactionManager 及 TransactionDefinition 和 TransactionStatus 三個核心類對事務進行操作,
使用事務管理器方式實作事務步驟:
- ① 獲取事務管理器 PlatformTransactionManager;
- ② 獲取事務屬性定義物件 TransactionDefinition;
- ③ 獲取事務狀態物件 TransactionStatus;
- ④ 業務資料操作處理;
- ⑤ 進行事務提交 commit 操作或者發生例外進行事務回滾 rollback 操作;
@Service
public class TransactionExample {
/** 1、獲取 PlatformTransactionManager 物件 **/
@Autowired
private PlatformTransactionManager platformTransactionManager;
public void addUser() {
// 2、獲取默認事務定義
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 設定事務傳播行為
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 3、根據事務定義物件設定的屬性,獲取事務狀態
TransactionStatus status = platformTransactionManager.getTransaction(def);
try {
// 4、執行業務代碼(這里進行模擬,執行多個資料庫操作方法)
userMapper.delete(1);
userMapper.delete(2);
// 5、事務進行提交
platformTransactionManager.commit(status);
} catch(Exception e){
// 5、事務進行回滾
platformTransactionManager.rollback(status);
}
}
}
Spring 宣告式事務
宣告式事務(declarative transaction management)顧名思義就是使用宣告的方式來處理事務,該方式是基于 Spring AOP 實作的,將具體業務邏輯和事務處理解耦,其本質是在執行方法前后進行攔截,在方法開始之前創建或者加入一個事務,在執行完目標方法之后根據執行情況提交或者回滾事務,
常用的宣告式事務使用方法
常用的宣告式事務使用方法有
- 1 XML
- 2 @Transactional 注解
兩種方法,由于近幾年 SpringBoot 的流行,提供很方便的自動化配置,致使 XML 方式已經逐漸淘汰,比較推薦使用注解的方式
@Transactional 的作用范圍
注解 @Transactional 不僅僅可以添加在方法上面,還可以添加到類級別上,當注解放在類級別時,表示所有該類的公共方法都配置相同的事務屬性資訊,如果類級別配置了 @transactional,方法級別也配置了 @transactional,應用程式會以方法級別的事務屬性資訊來管理事務,換言之,方法級別的事務屬性資訊會覆寫類級別的相關配置,
@Transactional 注解中可配置引數
value: 事務管理器,此配置項是設定 Spring 容器中的 Bean 名稱,這個 Bean 需要實作介面 PlatformTransactionManager,transactionManager: 事務管理器,該引數和 value 配置保持一致,是同一個東西,isolation: 事務隔離級別,默認為 Isolation.DEFAULT 級別propagation: 事務傳播行為,默認為 Propagation.REQUIREDtimeout: 事務超時時間,單位為秒,默認值為-1,當事務超時時會拋出例外,進行回滾操作,readOnly: 是否開啟只讀事務,是否開啟只讀事務,默認 falserollbackForClassName: 回滾事務的例外類名定義,同 rollbackFor,只是用類名定義,noRollbackForClassName: 指定發生哪些例外名不回滾事務,引數為類陣列,同 noRollbackFor,只是使用類的名稱定義,rollbackFor: 回滾事務例外類定義,當方法中出例外,且例外類和該引數指定的類相同時,進行回滾操作,否則提交事務,noRollbackFor: 指定發生哪些例外不回滾事務,當方法中出例外,且例外類和該引數指定的類相同時,不回滾而是將繼續提交事務,
示例
@Transactional(propagation=Propagation.REQUIRED)
public void test() {
//todo something
}
注意:
一般而言,不推薦將 @Transaction 配置到類上,因為這樣很可能使后來的維護人員必須強制使用事務,
使用事務時需要注意的點
- 1、遇到例外檢測不回滾,原因:默認RuntimeException級別才回滾,如果是Eexception級別的例外需要手動添加
@Transactional(rollbackFor=Exception.class)
- 2、捕捉例外后事物不生效,原因:捕捉處理了例外導致框架無法感知例外,自然就無法回滾了,建議:若非實際業務要求,則在業務層統一拋出例外,然后在控制層統一處理
@Transactional(rollbackFor=Exception.class)
public void test() {
try {
//業務代碼
} catch (Exception e) {
// TODO: handle exception
}
//主動捕捉例外導致框架無法捕獲,從而導致事物失效
}
總結
本章主要講了 事務基本概念ACID是什么
,臟讀、不可重復讀、幻讀 等等術語的介紹及例子
資料庫事務的隔離級別(4種),
Spring事務傳播行為(7種),事務的實作實體和使用事務時需要注意的地方 通過這篇文章,小伙伴們應該已經對事務有了更深的了解吧 ,最后 歡迎關注我的公眾號:JAVA寶典 或者加我的微信 我們一起探討和學習.
關注公眾號:java寶典
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/236908.html
標籤:Java
下一篇:精盡Spring MVC原始碼分析 - HandlerAdapter 組件(三)之 HandlerMethodArgumentResolver

