在高併發的資料庫操作中,DeadlockLoserDataAccessException 是開發者常遇到的棘手異常。本文將從場景還原、原理剖析、代碼級解決方案到死鎖產生機制,為你提供一套完整的應對指南。
當執行 UPDATE 操作時,應用拋出異常:Deadlock found when trying to get lock; try restarting transaction。雖然資料庫會自動回滾並重試,用戶僅感覺操作略微卡頓,但監控系統頻繁報警,必須從根源解決。
既然 InnoDB 已自動處理死鎖(回滾犧牲者),應用層只需實現「捕獲異常 → 等待隨機時間 → 重試」的邏輯。以下是基於 MyBatis 的通用封裝範例:
/**
* 處理更新操作中的死鎖重試
* @param mapper MyBatis Mapper 接口
* @param record 待更新的實體對象
* @throws InterruptedException 線程中斷異常
*/
private void updateWithDeadLock(TestMapper mapper, Test record) throws InterruptedException {
boolean deadlockOccurred;
int retries = 5; // 最大重試次數
do {
deadlockOccurred = false;
try {
mapper.updateByPrimaryKeySelective(record);
} catch (DeadlockLoserDataAccessException dlEx) {
deadlockOccurred = true;
// 隨機等待 0-500ms,避免重試風暴
Thread.sleep((long) (Math.random() * 500));
}
} while (deadlockOccurred && retries-- > 0);
}
關鍵點說明:
InnoDB 採用行級鎖,分為共享鎖(S Lock)與排他鎖(X Lock):
SELECT ... LOCK IN SHARE MODE 獲取,允許並發讀,阻塞寫。UPDATE、DELETE、SELECT ... FOR UPDATE 獲取,阻塞其他讀寫。死鎖經典場景:事務 A 持有 S 鎖請求 X 鎖,事務 B 持有 X 鎖請求 S 鎖,形成循環等待。
步驟 1:事務 A 開啟,獲取 S 鎖
mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
mysql> INSERT INTO t (i) VALUES(1);
mysql> START TRANSACTION;
mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE; -- 持有 S 鎖
步驟 2:事務 B 開啟,嘗試 DELETE 獲取 X 鎖(阻塞等待 S 鎖釋放)
mysql> START TRANSACTION;
mysql> DELETE FROM t WHERE i = 1; -- 需要 X 鎖,進入等待隊列
步驟 3:事務 A 同樣嘗試 DELETE(請求 X 鎖,但 B 已持有 X 鎖並等待 A 的 S 鎖)
mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
死鎖形成:
InnoDB 偵測到死鎖,選擇一個犧牲者(通常是持有較少鎖的事務)回滾,另一個事務繼續執行。
掌握死鎖原理與重試模式,讓你的應用在高併發下依然穩健運作。