應用拋出 TransactionTimedOutException,疑似表鎖,實則為**事務長時間處於 RUNNING 狀態未提交/回滾**,導致後續查詢被阻塞超時。本文記錄完整排查鏈路:現象 → 系統表分析 → Binlog 定位業務代碼 → 代碼修復驗證。
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Tue Nov 25 13:33:07 CST 2014 Caused by: ... Deadline was ... (Spring 事務管理器檢測到事務超過配置超時時間)
SQL 為簡單聯表查詢,無複雜邏輯。
SHOW ENGINE INNODB STATUS; -- 無死鎖信息 SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX; -- 關鍵發現
結果顯示多個事務 trx_state = RUNNING,trx_started 早於故障時間,trx_query = NULL (無活動語句),INNODB_LOCKS、INNODB_LOCK_WAITS 皆為空。**結論:非鎖阻塞,純屬事務懸掛未結束。**
KILL; -- 終結懸掛事務所在線程
INNODB_TRX 捕獲新懸掛事務 (thread_id=942663)。# Binlog 事件 BEGIN; INSERT INTO `SHOP_CASH_COUPON_USER_REF` ...; UPDATE `CASH_TICKET` ...; COMMIT; -- 時間點 17:41:10
該業務方法異常處理塊缺少 COMMIT 或 ROLLBACK,導致拋出異常後事務懸掛。Spring 聲明式事務默認僅對未檢查異常回滾,若捕獲異常未重拋或手動提交,事務不會自動結束。
// 修復前
try {
// DML 操作
} catch (Exception e) {
log.error(e); // 吞掉異常,事務懸掛
}
// 修復後
try {
// DML 操作
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 手動標記回滾
throw e; // 或重拋讓 Spring 捕獲回滾
}
修復部署後壓測 N 次,INNODB_TRX 無殘留 RUNNING 事務,應用無超時報錯。
Cannot execute statement: impossible to write to binary log since BINLOG_FORMAT = STATEMENT and at least one table uses a storage engine limited to row-based logging. InnoDB is limited to row-logging when transaction isolation level is READ COMMITTED or READ UNCOMMITTED.
解決: SET GLOBAL BINLOG_FORMAT = MIXED; (或 ROW),兼容 READ COMMITTED 隔離級。
推測:懸掛事務持有排他鎖 (X Lock) (源於前序 INSERT/UPDATE),後續 SELECT 需共享鎖 (S Lock),X 與 S 互斥導致阻塞。源碼層面涉及 lock_rec_convert_impl_to_expl、lock_wait_suspend_thread 等鎖等待邏輯,待後續深入。
INNODB_TRX 長事務告警 (超 60s 即報警)。ROLLBACK 或重拋異常交由框架管理。MIXED 平衡兼容性與復制一致性。@Transactional 配置 rollbackFor = Exception.class 覆蓋受檢異常。