實戰回顧
2018年11月28日 有兩個客戶在兩個渠道購買了同一產品每人各買2筆
系統應在29日做成交處理, 成交結束後, 更新一張記錄表, 記錄表根據產品代碼和渠道代碼作爲Unique.
成交使用已客戶爲維度的多線程成交.
// 方法名爲虛擬捏造, 並非實際使用方法名
成交方法 chengjiao() 爲獨立事務;
chengjiao() 方法內使用多線程的嵌套事務 NESTED doChengjiao()
僞代碼
// 獨立事務
chengjiao() {
// 根據客戶查出交易
List<Trade> lists = getTradeList();
new Thread ... (list);
}
// 嵌套事務
doChengjiao();
假如數據爲 渠道 001 產品 002 渠道 002 產品 002
那更新的記錄兩條線程都要取更新表裏面更新 001&002記錄 和 002&002;
但是問題出在線程的執行順序;
兩個客戶每個人在不同渠道買了一筆, 一共四筆交易記錄;
線程A先去更新了 001 & 002 這條記錄
線程B先去更新了 002 & 002 這條記錄
之後
線程B又去更新 001 & 002 這條記錄; (問題在這已經出現)
線程A去更新 002 & 002 這條記錄;
後續的線程B在更新的時候, 在等待這條記錄之前的UPDATE事務提交或回滾, 而在佔用這條記錄的線程A想要提交需要等待002 & 002 這條記錄提交或回滾, 而002 & 002這條記錄正好被B線程佔用, 由此造成了互相等待, 將更新表鎖住.後續交易無法進行.後經人爲干預(數小時後發現), 殺掉其中一條會話, 導致該會話數據回滾, 而另一個會話因爲數據庫等待時間過長, 數據也沒有進行提交, 最後導致4筆交易全部回滾. 如果問題出在這也就沒什麼. 問題是每天這幾筆交易都恰巧同時執行.就一直卡死. 最後在12月3號, 4筆交易成交了. 4個工作日.問題影響… 客戶是拒絕的… 不過好在客戶大度, 沒有計較. … …
至此將問題從生產日誌取下, 分析, 復現, 解決,重新上線 共計 2周+, 期間對spring事務感悟頗深. 遂總結此文章. 整理, 學習.
Sring 事務管理
首先來看事務的四個特性:
- 原子性
事務的執行將事務內所做的操作看做一個整體, 要麼全部執行, 要麼全部不執行.
- 隔離性 (可能導致死鎖)
簡單來說, 兩個事務在同時進行更新時,一個事務在更新, 另一個事務需要操作時,不可能看到這條記錄之前的值, 需要等到之前的事務要麼執行(事務提交),要麼不執行(事務回滾). 纔可以繼續對該記錄進行操作. 這也是事務的其中一個隔離級別, 也是默認最優隔離級別 READCOMMITED 讀已提交;
- 一致性
對於同處在一個事務中的數據而言. 需要保持所有的相關數據保持一致狀態, 當事務執行完以後也要保持相關全部數據的正確性
- 持久性
簡單來說, 事物的提交之後的數據保存到數據庫中, 進行持久化處理;
事務的4個隔離級別
隔離級別 | 髒讀 | 不可重讀 | 幻讀 |
---|---|---|---|
讀操作未提交 | 可能 | 可能 | 可能 |
讀操作已提交 | 不可能 | 可能 | 可能 |
可重讀 | 不可能 | 不可能 | 可能 |
串行化 | 不可能 | 不可能 | 不可能 |
事務的7個傳播機制
- REQUIRED: 如果存在一個事務,支持當前事務。如果沒有事務則開啓
- SUPPORTS: 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行
- NOT_SUPPORTED: 總是非事務地執行,並掛起任何存在的事務(不使用事務)
- NESTED: 如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, REQUIRED 屬性執行
- REQUIRES_NEW: 總是開啓一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起(自己一個事務,獨立事務)
- NEVER: 總是非事務地執行,如果存在一個活動事務,則拋出異常(必須由非事務的方法調用)
- MANDATORY: 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常(必須由帶有事務的方法來調用)
畫重點
spring的事務管理中, 讓我們容易出現問題的幾個傳播約定
- REQUIRED
- NESTED
1是默認傳播機制, 2是嵌套傳播機制;
REQUIRED 如果你沒有, 我就自己管自己, 如果有, 就用你的;
NESTED 如果有, 我就聽你的, 如果沒有, 我就按照默認的走;
舉例說明:
fun1() 方法1 是一個帶事務的方法, 我們將使用fun1()來調用, fun2(), 此時的方法2 fun2()我們在配置事務的時候
- 配置了一個 REQUIRED , 那麼此時的fun2()支持fun1()的事務, 與fun1() 事務相同, 你是什麼事務, 我就是什麼事務.
- 配置了一個NESTED, 那麼這個時候的fun2() 則是存在fun1()的事務之中, 而不是另起一個事務的存在. 他的提交與回滾, 與 fun1() 共存, fun1() 提交, 我就提交, fun1()回滾, 我就回滾;
當fun1() 方法1 不是一個帶事務的方法 , 此時 REQUIRED 與 NESTED 意義相同; 都將自身新啓事務. 獨立提交或回滾;