Spring事務太強大了,相容資料庫同時給我們提供多種組合應對業務需求

語言: CN / TW / HK

事物的由來

在mysql中只有innodb儲存引擎才支援事物,所以我們後續都是基於innodb來展開的

事物特性

  • 事物是用來保證資料的完整性的,保證批量sql執行的統一性;事物具有四個特性: A(Atomicity)、C(Consistency)、I(Isolation)、D(Durability)

  • 原子性

    一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。

  • 一致性

    在事務開始之前和事務結束以後,資料庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續資料庫可以自發性地完成預定的工作。

  • 隔離性

    資料庫允許多個併發事務同時對其資料進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致資料的不一致。

  • 永續性

    事務處理結束後,對資料的修改就是永久的,即便系統故障也不會丟失。

  • 基於四個特性,我們就需要在資料和效能進行權衡,需要比較下場景。在m y s q l中就存在四種隔離級別。事務隔離分為不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和序列化(Serializable)。

| 隔離級別 | 髒讀(Dirty Read) | 不可重複讀(NonRepeatable Read) | 幻讀(Phantom Read) | | ---------------------- | -------------- | ------------------------- | ---------------- | | 未提交讀(Read uncommitted) | 可能 | 可能 | 可能 | | 已提交讀(Read committed) | 不可能 | 可能 | 可能 | | 可重複讀(Repeatable read) | 不可能 | 不可能 | 可能 | | 可序列化(Serializable ) | 不可能 | 不可能 | 不可能 |

  • 之前我一直錯誤的認為mysql中預設的是讀已提交。後來查閱資料才知道預設的是可重複讀隔離級別。

什麼事髒讀、不可重複讀、幻讀

  • 上面提到了三種異常資料情況,作為一名合格的程式設計師我們需要知道不同的隔離級別會給我們造成具體什麼樣的傷害。

    髒讀

    • 髒讀指的是A事物在操作資料,還未進行提交此時B事物就已經感知到資料的變化了。這是我們最不能接受的。這裡以個人舉例,大學期間看中一款手機但是囊中羞澀需要爸媽支援下,打電話讓爸媽賺錢這裡我們理解成A事物,我著急用錢我是B事物,此時A事物向zxh這張表新增了money=5000元的一條資料。B事物在修改zxh這張表中money這個欄位。如果我們是讀未提交隔離級別。那麼A事物剛新增完事物還未提交,B事物卻已經可以修改了。那麼B事物修改成功後就結束了。這個時候如果A事物因為別的原因出現異常了。那麼就需要將add的5000進行回滾,回滾後餘額出現負數。這個負數對於銀行來說就是壞賬。

image-20220123112618285.png

#### 不可重複讀

-   顧名思義就是同一條資料多次讀取並不能保證資料一致性。這是因為A事物第一次讀完資料後B事物修改了相同的資料行。那麼A事物在此讀取該資料就會發生不可重複讀。

#### 幻讀

-   不可重複讀是針對同一條資料內容的變化。而幻讀針對的是表記錄的變化。還是A事物第一次查詢資料匹配到10條,第二次相同條件匹配到的是20條。這就是幻讀。

查詢

| s q l | 功能 | | ---------------------------------------------------------- | ----------- | | select @@tx_isolation; | 查詢資料庫預設隔離級別 | | select @@global.tx_isolation; | 檢視系統當前隔離級別 | | set session transaction isolation level repeatable read; | 設定當前會話隔離級別 | | set global transaction isolation level repeatable read; | 設定系統當前隔離級別 |

  • mysql 四種隔離級別對應關係 ; 設定時候去掉破折號就行了

REPEATABLE-READ

READ-COMMITTED

READ-UNCOMMITTED

SERIALIZABLE

  • 我們也可以通過show來檢視相關配置

show variables like '%iso%';

spring事物

@Override  public Map<String, Object> startTransaction(Transmodel transmodel) {    int insert = transMapper.insert(transmodel);    log.info("操作日誌:" + insert);    if ("zxhtom".equals(transmodel.getName())) {      throw new RuntimeException("error");   }    return new HashMap<>();  }

  • 上面程式碼邏輯非常的簡單,當我們傳送如下引數時就會報錯

{      "name": "zxhtom",      "code": "56",      "age": 34  }

  • 但是呢?我們再去資料庫檢視,發現該資料進入資料庫了。

image-20220123165751122.png

  • 發生這種場景時不行的,在網際網路中我們要求部分介面必須具有原子性,中途報錯之前所有的操作就必須回滾。如果我們自己使用jdbc那麼直接通過連線物件rollback就行了。
  • 在spring中因為連線session對接管了,我們就不必那麼麻煩了,直接通過@Transactional標註我們的方法就可以實現事物了。

@Transactional  public Map<String, Object> startTransaction(Transmodel transmodel) {    int insert = transMapper.insert(transmodel);    log.info("操作日誌:" + insert);    if ("zxhtom".equals(transmodel.getName())) {      throw new RuntimeException("error");   }    return new HashMap<>();  }

  • 在此執行就會發現,資料並沒有新增,這就是我們想要的效果

spring事物有哪些可配項

image-20220123170516789.png

  • 裡面有幾個屬性我們需要關注下。已經圈出來了。

image-20220123171940651.png

| 欄位 | 作用 | | ---------------------- | --------------------------------------------------------------------- | | value | 事物管理器;用於提交和回滾的功能;預設的DataSourceTransactionManager內部時datasource來操作提交回滾的 | | transactionManager | 事物管理器;用於提交和回滾的功能;預設的DataSourceTransactionManager內部時datasource來操作提交回滾的 | | propagation | 傳播屬性;spring中方法內方法事物傳遞屬性設定 | | isolation | 隔離級別,在資料庫基礎上新增一個預設以資料庫隔離級別為準 | | timeout | 超時事物不提交 | | readOnly | 這個感覺沒啥意義,被標註的方法無法執行更新操作,只能select | | rollbackFor | 導致事務回滾的異常類陣列 | | rollbackForClassName | 導致事務回滾的異常類名字陣列 | | noRollbackFor | 不會導致事務回滾的異常類陣列 | | noRollbackForClassName | 不會導致事務回滾的異常類名字陣列 |

傳播屬性

  • 傳播屬性spring為我們建立了列舉

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);  ​  ​   private final int value;  ​  ​   Propagation(int value) {   this.value = value;   }  ​   public int value() {   return this.value;   }  ​  }

山水有相逢,咱們【隔離級別】繼續

我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿。

「其他文章」