一文搞懂什麼是事務
[toc]
事務概念我們要理解下事務概念: 什麼是事務呢?事務是併發控制的單位,是使用者定義的一個操作序列。有四個特性(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 引擎會給讀操作隱式加一把讀共享鎖,從而避免了髒讀、不可重讀復讀和幻讀問題。
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 之上的。其本質是對方法前後進行攔截,然後在目標方法開始之前建立或者加入一個事務,在執行完目標方法之後根據執行情況“提交”或者“回滾”事務。
一般來說程式設計式事務有兩種方法可以實現:模板事務的方式(TransactionTemplate)
和 平臺事務管理器方式(PlatformTransactionManager)
例:
① 獲取模板物件 TransactionTemplate;② 選擇事務結果型別;③ 業務資料操作處理;④ 業務執行完成事務提交或者發生異常進行回滾;其中 TransactionTemplate 的 execute 能接受兩種型別引數執行事務,分別為:
TransactionCallback<Object>(): 執行事務且可以返回一個值。 TransactionCallbackWithoutResult(): 執行事務沒有返回值。
下面是使用 TransactionTemplate 的例項:
@Servicepublic 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 操作;@Servicepublic 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.REQUIRED
timeout
: 事務超時時間,單位為秒,預設值為-1,當事務超時時會丟擲異常,進行回滾操作。readOnly
: 是否開啟只讀事務,是否開啟只讀事務,預設 false
rollbackForClassName
: 回滾事務的異常類名定義,同 rollbackFor,只是用類名定義。noRollbackForClassName
: 指定發生哪些異常名不回滾事務,引數為類陣列,同 noRollbackFor,只是使用類的名稱定義。rollbackFor
: 回滾事務異常類定義,當方法中出異常,且異常類和該引數指定的類相同時,進行回滾操作,否則提交事務。noRollbackFor
: 指定發生哪些異常不回滾事務,當方法中出異常,且異常類和該引數指定的類相同時,不回滾而是將繼續提交事務。示例@Transactional(propagation=Propagation.REQUIRED)public void test() { //todo something}
注意:一般而言,不推薦將 @Transaction 配置到類上,因為這樣很可能使後來的維護人員必須強制使用事務。使用事務時需要注意的點
1、遇到異常檢測不回滾,原因:預設RuntimeException級別才回滾,如果是Eexception級別的異常需要手動新增
@Transactional(rollbackFor=Exception.class)
2、捕捉異常後事物不生效,原因:捕捉處理了異常導致框架無法感知異常,自然就無法回滾了。建議:若非實際業務要求,則在業務層統一丟擲異常,然後在控制層統一處理