1. 程式人生 > 實用技巧 >MySQL-事務相關知識

MySQL-事務相關知識

事務ACID的理解

引入事務的主要目的:

  • 保證資料庫從一個一致性狀態切換為另一種一致性狀態
  • 所有修改要麼都儲存,要麼都不儲存
A 原子性

原子性關注單個事務的整體性,需要保證事務中的全部操作是一個單元,要麼都成功,要麼都失敗。

C 一致性

一致性關注,事務開始和結束後,資料庫的完整性約束沒有被破壞。簡單理解,如果有外來鍵的存在,事務執行前,外來鍵可以保證一致性約束;事務執行之後,這個一致性約束也不應該被破壞。

I 隔離性

隔離性關注多個事務之間是否彼此互相不知曉,不影響。

沒有隔離性帶來的問題

沒有隔離級別的時候,多個事務互相交替執行,會出現一些問題:

  • 髒讀:讀取到其他事務未提交的資料
  • 不可重複讀:在事務的開始和結束這段時間,對同一行資料,讀取到的值是不同的
  • 幻讀:事務開始和結束這段時間,同一個查詢,查詢到的資料行數不一致
四種隔離級別

四種隔離級別,可以分別解決不同的隔離性問題:

  • 讀未提交:事務可以讀到其他事務未提交的資料
    • 有髒讀,不可重複讀,幻讀問題
  • 讀已提交:事務可以讀到其他事務已經提交的資料
    • 解決髒讀問題,仍有不可重複讀,幻讀問題
  • 可重複讀:事務在開始到結束這段時間,讀到的同一行資料是一致的。
    • 解決髒讀、不可重複讀,仍有幻讀問題
  • 序列化:事務和事務之間是序列執行的
    • 所有問題都可以解決

這四種隔離級別的隔離讀,從上到下越來越高,但是效能從上到下越來越低。

隔離性的實現

實現上,資料庫會建立一個檢視,訪問的時候以這個檢視的邏輯結果為準。

  • 讀未提交:不建立檢視
  • 讀已提交:在事務中的每條sql語句執行前建立檢視
  • 可重複讀:事務開始時建立,整個事務執行過程中都已這個檢視為準
  • 序列化:直接使用加鎖的方式實現

在每一次更新操作以後,會記錄一條回滾日誌,當前的最新值,可以通過回滾日誌回滾到之前的某一個狀態,MVCC(多版本併發控制)就是通過回滾段實現的,同一條記錄在系統中存在多個版本,不同時刻啟動的事務,檢視到的就是不同的版本值,並且版本之間互不干擾。

回滾日誌是會被刪除的,當系統判斷,沒有事務會用到這些回滾日誌的時候就會刪除回滾日誌,也就是說當這個系統中沒有比這個回滾日誌更早的事務檢視的時候。

可重複讀的適用場景

可重複讀,適用於對賬場景,在對賬過程中,其他事務對資料庫的更改,不會影響校對結果。


最佳實踐

  1. 基於回滾日誌的描述,不建議使用長事務,長事務意味著系統中會存在很多很老的回滾日誌,會佔用大量儲存空間
    • 另外,長事務本身可能需要非常長的時間來執行,當事務中出現問題需要回滾時,回滾本身的時間也會很長
  2. 建議保持autocomit為1,用手動開啟事務的方式來使用事務。
    • begin來顯示開啟一個事務,mysql會自動執行set autocommit = 0;在commit或rollback後會set autocommit = 1;
    • 如果autocommit為0,每次都需要手動設定commit或rollback,不需要事務的時候也需要提交
    • autocommit為1,有些sql語句可以自動提交,防止出現長事務。
  3. 如何規避長事務
    1. 針對業務開發人員,系統培訓長事務相關知識
    2. code review中,檢查程式碼以及資料庫相關配置資訊
    3. 測試人員,建立相關測試用例
    4. 運維建立長事務監控指令碼
  4. 長事務出現後的運維:長事務識別、處理,監控

生產專案分析

專案簡介

目前的專案,使用springboot結合mybatis做資料庫操作,事務管理使用Transactional註解實現,springboot預設使用的hikari pool連線池獲取連線,該連線池autocommit屬性值預設為true。在開啟了事務的方法中,springboot會將連線的autocommit設定為false,程式碼如下:

類檔案:org/springframework/jdbc/datasource/DataSourceTransactionManager.java

// switch to manual commit if necessary. this is very expensive in some jdbc drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
if (con.getautocommit()) {
    txobject.setmustrestoreautocommit(true);
    if (logger.isdebugenabled()) {
        logger.debug("switching jdbc connection [" + con + "] to manual commit");
    }
    con.setautocommit(false);
}

在完成之後,如果autocommit為true,會自動恢復。

if (txObject.isMustRestoreAutoCommit()){    
    con.setAutoCommit(true);
}
事務失效問題

在生產實踐中,很容易出現事務失效的問題,具體表現為呼叫事務方法,出異常後未回滾。編寫程式碼時,按照下面的規範執行,就不會出現這個問題了。

A方法和B方法都有事務時,A方法呼叫B方法

  1. 如果A方法和B方法在同一個類中,需要通過容器呼叫B方法
  2. 如果A方法和B方法不在同一個類中,直接呼叫即可

A方法無事務,B方法有事務

  1. 只要A方法保證通過容器呼叫B方法,事務就一定會生效

具體可以參考這篇部落格:
巢狀事務總結