一文搞懂什么是事務(wù)
一文搞懂什么是事務(wù)
一文搞懂什么是事務(wù)
事務(wù)概念
臟讀、不可重復(fù)讀、幻讀
數(shù)據(jù)庫事務(wù)的隔離級別
Spring事務(wù)傳播行為
Spring 事務(wù)的兩種實現(xiàn)
使用事務(wù)時需要注意的點
總結(jié)
事務(wù)概念
我們要理解下事務(wù)概念:什么是事務(wù)呢?事務(wù)是并發(fā)控制的單位,是用戶定義的一個操作序列。有四個特性(ACID):
原子性(Atomicity):事務(wù)是數(shù)據(jù)庫的邏輯工作單位,事務(wù)中包括的諸操作要么全做,要么全不做。
一致性(Consistency):事務(wù)執(zhí)行的結(jié)果必須是使數(shù)據(jù)庫從一個一致性狀態(tài)變到另一個一致性狀態(tài)。一致性與原子性是密切相關(guān)的。
隔離性(Isolation):一個事務(wù)的執(zhí)行不能被其他事務(wù)干擾。
持續(xù)性/永久性(Durability):一個事務(wù)一旦提交,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就應(yīng)該是永久性的。
以上是書面解釋,簡單來說就是把你的操作統(tǒng)一化,要么所有操作都成功,要么就都不成功,如果執(zhí)行中有某一項操作失敗,其之前所有的操作都回滾到未執(zhí)行這一系列操作之前的狀態(tài)。
臟讀、不可重復(fù)讀、幻讀
先理解這三種由于并發(fā)訪問導(dǎo)致的數(shù)據(jù)讀取問題,再理解事務(wù)隔離級別就簡單多了。
臟讀
A事務(wù)讀取B事務(wù)尚未提交的數(shù)據(jù),此時如果B事務(wù)發(fā)生錯誤并執(zhí)行回滾操作,那么A事務(wù)讀取到的數(shù)據(jù)就是臟數(shù)據(jù)。就好像原本的數(shù)據(jù)比較干凈、純粹,此時由于B事務(wù)更改了它,這個數(shù)據(jù)變得不再純粹。這個時候A事務(wù)立即讀取了這個臟數(shù)據(jù),但事務(wù)B良心發(fā)現(xiàn),又用回滾把數(shù)據(jù)恢復(fù)成原來干凈、純粹的樣子,而事務(wù)A卻什么都不知道,最終結(jié)果就是事務(wù)A讀取了此次的臟數(shù)據(jù),稱為臟讀。
這種情況常發(fā)生于轉(zhuǎn)賬與取款操作中

不可重復(fù)讀(前后多次讀取,數(shù)據(jù)內(nèi)容不一致)
事務(wù)A在執(zhí)行讀取操作,由整個事務(wù)A比較大,前后讀取同一條數(shù)據(jù)需要經(jīng)歷很長的時間 。而在事務(wù)A第一次讀取數(shù)據(jù),比如此時讀取了小明的年齡為20歲,事務(wù)B執(zhí)行更改操作,將小明的年齡更改為30歲,此時事務(wù)A第二次讀取到小明的年齡時,發(fā)現(xiàn)其年齡是30歲,和之前的數(shù)據(jù)不一樣了,也就是數(shù)據(jù)不重復(fù)了,系統(tǒng)不可以讀取到重復(fù)的數(shù)據(jù),成為不可重復(fù)讀。

幻讀(前后多次讀取,數(shù)據(jù)總量不一致)
事務(wù)A在執(zhí)行讀取操作,需要兩次統(tǒng)計數(shù)據(jù)的總量,前一次查詢數(shù)據(jù)總量后,此時事務(wù)B執(zhí)行了新增數(shù)據(jù)的操作并提交后,這個時候事務(wù)A讀取的數(shù)據(jù)總量和之前統(tǒng)計的不一樣,就像產(chǎn)生了幻覺一樣,平白無故的多了幾條數(shù)據(jù),成為幻讀。

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

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