記一次Spring @Transactional失效的排查過程
阿新 • • 發佈:2019-12-31
問題
壓力測試時發現生成了相同的序號,根據日誌分析發現select ... for update沒有鎖住某一行的資料,從而導致序號重複
排查
- 十分確定select ... for update的互斥機制,該行資料必定被鎖,其他select ... for update/update/delete等SQL需要等待鎖才能執行後續操作.
- 首先懷疑事務是否起作用,使用TransactionSynchronizationManager#getCurrentTransactionName列印當前的事務,結果竟然是null!!!沒有事務自然不會互斥,能解釋為什麼生成重複序號了.
- 剩下就是為什麼明明加了@Transactional註解,但是沒有開啟事務了.
- 帶著問題檢查程式碼,有問題的程式碼如下圖.這是一個責任鏈的設計模式,對於責任鏈的任一個節點,先呼叫doProcess執行子類自身的邏輯,如果成功則執行下一個succ節點,報錯則執行下一個fail節點,直到責任鏈結束.
- 找到了宣告事務的doProcess方法,他是在process裡用this.doProcess調起的,我們都知道Spring的AOP事務要生效,必須使用 另一物件.方法 的形式才有效,這就是@Transactional失效的原因. 簡而言之,不能在同一個類中使用沒有@Transactional的方法調起有@Transactional的方法
public abstract class BaseController {
private ScBaseController succNext = null;
private ScBaseController failNext = null;
public DataMessage process(DataMessage msg) throws ScBaseException {
DataMessage respMsg = this.doProcess(msg);
// 執行成功
if (ExecuteResultEnum.SUCCESS.equals(respMsg.getExecuteResult())) {
if (this.succNext != null) {
return this.succNext.process(respMsg);
} else {
return respMsg;
}
}
// 執行失敗
else {
if (this.failNext != null) {
return this.failNext.process(respMsg);
} else {
return respMsg;
}
}
}
@Transactional // 這裡的事務註解沒有作用
public abstract DataMessage doProcess(DataMessage msg);
複製程式碼
教訓
-
看過很多文章都說過不能在同一個類中使用沒有@Transactional的方法調起有@Transactional的方法,這個是Spring動態AOP的機制決定的,必須緊記這個教訓.至於為什麼動態AOP會這樣,後續如果寫到這方面的文章再詳細說明.
-
幸好在壓力測試中發現這個問題,並沒有造成線上故障.有些問題不是做幾筆交易就能說明清楚,必須在高併發高壓力的測試下才能驗證系統的健壯性.