MySQL-事務相關知識
事務ACID的理解
引入事務的主要目的:
- 保證資料庫從一個一致性狀態切換為另一種一致性狀態
- 所有修改要麼都儲存,要麼都不儲存
A 原子性
原子性關注單個事務的整體性,需要保證事務中的全部操作是一個單元,要麼都成功,要麼都失敗。
C 一致性
一致性關注,事務開始和結束後,資料庫的完整性約束沒有被破壞。簡單理解,如果有外來鍵的存在,事務執行前,外來鍵可以保證一致性約束;事務執行之後,這個一致性約束也不應該被破壞。
I 隔離性
隔離性關注多個事務之間是否彼此互相不知曉,不影響。
沒有隔離性帶來的問題
沒有隔離級別的時候,多個事務互相交替執行,會出現一些問題:
- 髒讀:讀取到其他事務未提交的資料
- 不可重複讀:在事務的開始和結束這段時間,對同一行資料,讀取到的值是不同的
- 幻讀:事務開始和結束這段時間,同一個查詢,查詢到的資料行數不一致
四種隔離級別
四種隔離級別,可以分別解決不同的隔離性問題:
- 讀未提交:事務可以讀到其他事務未提交的資料
- 有髒讀,不可重複讀,幻讀問題
- 讀已提交:事務可以讀到其他事務已經提交的資料
- 解決髒讀問題,仍有不可重複讀,幻讀問題
- 可重複讀:事務在開始到結束這段時間,讀到的同一行資料是一致的。
- 解決髒讀、不可重複讀,仍有幻讀問題
- 序列化:事務和事務之間是序列執行的
- 所有問題都可以解決
這四種隔離級別的隔離讀,從上到下越來越高,但是效能從上到下越來越低。
隔離性的實現
實現上,資料庫會建立一個檢視,訪問的時候以這個檢視的邏輯結果為準。
- 讀未提交:不建立檢視
- 讀已提交:在事務中的每條sql語句執行前建立檢視
- 可重複讀:事務開始時建立,整個事務執行過程中都已這個檢視為準
- 序列化:直接使用加鎖的方式實現
在每一次更新操作以後,會記錄一條回滾日誌,當前的最新值,可以通過回滾日誌回滾到之前的某一個狀態,MVCC(多版本併發控制)就是通過回滾段實現的,同一條記錄在系統中存在多個版本,不同時刻啟動的事務,檢視到的就是不同的版本值,並且版本之間互不干擾。
回滾日誌是會被刪除的,當系統判斷,沒有事務會用到這些回滾日誌的時候就會刪除回滾日誌,也就是說當這個系統中沒有比這個回滾日誌更早的事務檢視的時候。
可重複讀的適用場景
可重複讀,適用於對賬場景,在對賬過程中,其他事務對資料庫的更改,不會影響校對結果。
最佳實踐
- 基於回滾日誌的描述,不建議使用長事務,長事務意味著系統中會存在很多很老的回滾日誌,會佔用大量儲存空間
- 另外,長事務本身可能需要非常長的時間來執行,當事務中出現問題需要回滾時,回滾本身的時間也會很長
- 建議保持autocomit為1,用手動開啟事務的方式來使用事務。
- begin來顯示開啟一個事務,mysql會自動執行set autocommit = 0;在commit或rollback後會set autocommit = 1;
- 如果autocommit為0,每次都需要手動設定commit或rollback,不需要事務的時候也需要提交
- autocommit為1,有些sql語句可以自動提交,防止出現長事務。
- 如何規避長事務
- 針對業務開發人員,系統培訓長事務相關知識
- code review中,檢查程式碼以及資料庫相關配置資訊
- 測試人員,建立相關測試用例
- 運維建立長事務監控指令碼
- 長事務出現後的運維:長事務識別、處理,監控
生產專案分析
專案簡介
目前的專案,使用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方法
- 如果A方法和B方法在同一個類中,需要通過容器呼叫B方法
- 如果A方法和B方法不在同一個類中,直接呼叫即可
A方法無事務,B方法有事務
- 只要A方法保證通過容器呼叫B方法,事務就一定會生效
具體可以參考這篇部落格:
巢狀事務總結