多線程事務死鎖問題分析總結(實戰應用)

實戰回顧

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 事務管理

首先來看事務的四個特性:

  1. 原子性

    事務的執行將事務內所做的操作看做一個整體, 要麼全部執行, 要麼全部不執行.

  2. 隔離性 (可能導致死鎖)

    簡單來說, 兩個事務在同時進行更新時,一個事務在更新, 另一個事務需要操作時,不可能看到這條記錄之前的值, 需要等到之前的事務要麼執行(事務提交),要麼不執行(事務回滾). 纔可以繼續對該記錄進行操作. 這也是事務的其中一個隔離級別, 也是默認最優隔離級別 READCOMMITED 讀已提交;

  3. 一致性

    對於同處在一個事務中的數據而言. 需要保持所有的相關數據保持一致狀態, 當事務執行完以後也要保持相關全部數據的正確性

  4. 持久性

    簡單來說, 事物的提交之後的數據保存到數據庫中, 進行持久化處理;

事務的4個隔離級別

隔離級別 髒讀 不可重讀 幻讀
讀操作未提交 可能 可能 可能
讀操作已提交 不可能 可能 可能
可重讀 不可能 不可能 可能
串行化 不可能 不可能 不可能

事務的7個傳播機制

  1. REQUIRED: 如果存在一個事務,支持當前事務。如果沒有事務則開啓
  2. SUPPORTS: 如果存在一個事務,支持當前事務。如果沒有事務,則非事務的執行
  3. NOT_SUPPORTED: 總是非事務地執行,並掛起任何存在的事務(不使用事務)
  4. NESTED: 如果一個活動的事務存在,則運行在一個嵌套的事務中. 如果沒有活動事務, REQUIRED 屬性執行
  5. REQUIRES_NEW: 總是開啓一個新的事務。如果一個事務已經存在,則將這個存在的事務掛起(自己一個事務,獨立事務)
  6. NEVER: 總是非事務地執行,如果存在一個活動事務,則拋出異常(必須由非事務的方法調用)
  7. MANDATORY: 如果已經存在一個事務,支持當前事務。如果沒有一個活動的事務,則拋出異常(必須由帶有事務的方法來調用)

畫重點

spring的事務管理中, 讓我們容易出現問題的幾個傳播約定

  1. REQUIRED
  2. NESTED

1是默認傳播機制, 2是嵌套傳播機制;
REQUIRED 如果你沒有, 我就自己管自己, 如果有, 就用你的;
NESTED 如果有, 我就聽你的, 如果沒有, 我就按照默認的走;

舉例說明:
fun1() 方法1 是一個帶事務的方法, 我們將使用fun1()來調用, fun2(), 此時的方法2 fun2()我們在配置事務的時候

  1. 配置了一個 REQUIRED , 那麼此時的fun2()支持fun1()的事務, 與fun1() 事務相同, 你是什麼事務, 我就是什麼事務.
  2. 配置了一個NESTED, 那麼這個時候的fun2() 則是存在fun1()的事務之中, 而不是另起一個事務的存在. 他的提交與回滾, 與 fun1() 共存, fun1() 提交, 我就提交, fun1()回滾, 我就回滾;

當fun1() 方法1 不是一個帶事務的方法 , 此時 REQUIRED 與 NESTED 意義相同; 都將自身新啓事務. 獨立提交或回滾;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章