1.事故背景
原本在使用的是注解式事務,后面因為需要在事務中增加異步推送機制,所以需要將推送機制放到事務之外,修改后發現系統經常出現事務長時間無法提交導致回滾。
2.排查流程
(1)一開始重啟應用是能恢復正常,所以肯定是在某種情況下會觸發異常的產生
(2)查看在mysql控制台查看當前正在執行的事務(SELECT * FROM information_schema.INNODB_TRX),分析該sql語句在邏輯上並沒有鎖競爭的出現,只是單單一條update語句,但事務卻沒有提交
(3)這時候確定在業務代碼邏輯上不會出現鎖競爭,但事務卻沒有正常提交,所以考慮是mysql連接會話的autoCommit屬性為false導致事務無法正常提交
(4)因異常出現是在講注解式事務改為編程式事務之后,所以猜測是因為該改動導致異常出現
3.原理分析
(1)spring事務支持原理:spring的事務支持原理是先將mysql連接會話的自動提交屬性關閉,即將當前會話的autoCommit屬性設置為false,然后將該連接綁定到該線程中,在該事務中的所有數據庫操作都是使用同一個線程,所有的數據庫操作完成后才主動去做commit操作完成事務
(2)以下為編程式事務出現異常的流程分析的代碼示例
當開啟事務時,當前會話的自動提交屬性講被設置為false
當事務沒有提交而是提前return出去時,會話的狀態並不會改變,autoCommit屬性一直為false,這就導致當其他請求使用該數據庫連接會話操作數據庫時,事務將無法自動提交,如下圖所示,即使沒有開啟事務,
autoCommit狀態也是為false,所以會導致其他使用該會話的事務無法正常提交
4.改進方式
(1)避免在未主動commit事務前return出去
(2)增加finally代碼塊,判斷事務狀態,回滾事務即可
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition(); TransactionStatus transactionStatus = transactionManager.getTransaction(defaultTransactionDefinition); try { doSomething(); } catch (Exception e) { transactionManager.rollback(transactionStatus); getLogger().error(e.getMessage()); throw new RuntimeException("系統異常"); } finally { if(null != transactionStatus && !transactionStatus.isCompleted()){ transactionManager.rollback(transactionStatus); } }
連接恢復源碼如下: