一文搞懂什麼是事務

一文搞懂什麼是事務

事務概念

咱們要理解下事務概念: 什麼是事務呢?事務是併發控制的單位,是用戶定義的一個操做序列。有四個特性(ACID):html

  • 原子性(Atomicity): 事務是數據庫的邏輯工做單位,事務中包括的諸操做要麼全作,要麼全不作。java

  • 一致性(Consistency): 事務執行的結果必須是使數據庫從一個一致性狀態變到另外一個一致性狀態。一致性與原子性是密切相關的。mysql

  • 隔離性(Isolation): 一個事務的執行不能被其餘事務干擾。sql

  • 持續性/永久性(Durability): 一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。數據庫

以上是書面解釋,簡單來講就是把你的操做統一化,要麼全部操做都成功,要麼就都不成功,若是執行中有某一項操做失敗,其以前全部的操做都回滾到未執行這一系列操做以前的狀態。編程

髒讀、不可重複讀、幻讀

先理解這三種因爲併發訪問致使的數據讀取問題,再理解事務隔離級別就簡單多了。數組

髒讀

A事務讀取B事務還沒有提交的數據,此時若是B事務發生錯誤並執行回滾操做,那麼A事務讀取到的數據就是髒數據。就好像本來的數據比較乾淨、純粹,此時因爲B事務更改了它,這個數據變得再也不純粹。這個時候A事務當即讀取了這個髒數據,但事務B良心發現,又用回滾把數據恢復成原來乾淨、純粹的樣子,而事務A卻什麼都不知道,最終結果就是事務A讀取了這次的髒數據,稱爲髒讀。微信

這種狀況常發生於轉帳與取款操做中併發

不可重複讀(先後屢次讀取,數據內容不一致)

事務A在執行讀取操做,由整個事務A比較大,先後讀取同一條數據須要經歷很長的時間 。而在事務A第一次讀取數據,好比此時讀取了小明的年齡爲20歲,事務B執行更改操做,將小明的年齡更改成30歲,此時事務A第二次讀取到小明的年齡時,發現其年齡是30歲,和以前的數據不同了,也就是數據不重複了,系統不能夠讀取到重複的數據,成爲不可重複讀。app

幻讀(先後屢次讀取,數據總量不一致)

事務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 {
  /** 一、獲取 TransactionTemplate 對象 **/
  @Autowired
  private TransactionTemplate transactionTemplate;
  
  public void addUser() {
      // 二、使用 TransactionCallback 或者 TransactionCallbackWithoutResult 執行事務
      transactionTemplate.execute(new TransactionCallbackWithoutResult() {
          @Override
          public void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
              try {
                  // 三、執行業務代碼(這裏進行模擬,執行多個數據庫操做方法)
                  userMapper.delete(1);
                  userMapper.delete(2);
              } catch (Exception e) {
                  // 四、發生異常,進行回滾
                  transactionStatus.setRollbackOnly();
              }
          }
      });
  }
  
}

  • 平臺事務管理器方式(PlatformTransactionManager): 這裏使用最基本的事務管理局對事務進行管理,藉助 Spring 事務的 PlatformTransactionManager 及 TransactionDefinition 和 TransactionStatus 三個核心類對事務進行操做。

使用事務管理器方式實現事務步驟:

  • ① 獲取事務管理器 PlatformTransactionManager;
  • ② 獲取事務屬性定義對象 TransactionDefinition;
  • ③ 獲取事務狀態對象 TransactionStatus;
  • ④ 業務數據操做處理;
  • ⑤ 進行事務提交 commit 操做或者發生異常進行事務回滾 rollback 操做;
@Service
public class TransactionExample {
    
    /** 一、獲取 PlatformTransactionManager 對象 **/
    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    public void addUser() {
        // 二、獲取默認事務定義
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // 設置事務傳播行爲
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 三、根據事務定義對象設置的屬性,獲取事務狀態
        TransactionStatus status = platformTransactionManager.getTransaction(def);
        try {
            // 四、執行業務代碼(這裏進行模擬,執行多個數據庫操做方法)
            userMapper.delete(1);
            userMapper.delete(2);
            // 五、事務進行提交
            platformTransactionManager.commit(status);
        } catch(Exception e){
            // 五、事務進行回滾
            platformTransactionManager.rollback(status);
        }
    }
    
}

Spring 聲明式事務

聲明式事務(declarative transaction management)顧名思義就是使用聲明的方式來處理事務。該方式是基於 Spring AOP 實現的,將具體業務邏輯和事務處理解耦,其本質是在執行方法先後進行攔截,在方法開始以前建立或者加入一個事務,在執行完目標方法以後根據執行狀況提交或者回滾事務。

經常使用的聲明式事務使用方法

經常使用的聲明式事務使用方法有

兩種方法,因爲近幾年 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 配置到類上,由於這樣極可能使後來的維護人員必須強制使用事務。

使用事務時須要注意的點

  • 一、遇到異常檢測不回滾,緣由:默認RuntimeException級別纔回滾,若是是Eexception級別的異常須要手動添加
@Transactional(rollbackFor=Exception.class)
  • 二、捕捉異常後事物不生效,緣由:捕捉處理了異常致使框架沒法感知異常,天然就沒法回滾了。建議:若非實際業務要求,則在業務層統一拋出異常,而後在控制層統一處理
@Transactional(rollbackFor=Exception.class)
public void test() {
    try {
         //業務代碼
    } catch (Exception e) {
        // TODO: handle exception
    }
   //主動捕捉異常致使框架沒法捕獲,從而致使事物失效
}

總結

本章主要講了 事務基本概念ACID是什麼 ,髒讀、不可重複讀、幻讀 等等術語的介紹及例子 數據庫事務的隔離級別(4種), Spring事務傳播行爲(7種),事務的實現實例和使用事務時須要注意的地方 經過這篇文章,小夥伴們應該已經對事務有了更深的瞭解吧 ,最後 歡迎關注個人公衆號:JAVA寶典 或者加個人微信 咱們一塊兒探討和學習.

關注公衆號:java寶典 a

相關文章
相關標籤/搜索