數據庫死鎖處理與重試機制實現指南
1. 業務場景
1.1 問題現象
- 高并發批量數據處理時頻繁出現數據庫死鎖
- 主要發生在"先刪除歷史數據,再重新計算"的業務流程中
- 原有逐條處理方式:
list.forEach(item -> { delete(); calculate(); })
1.2 死鎖原因分析
- 鎖競爭:多個線程同時對相同數據進行刪除和插入操作
- 事務時間過長:刪除和計算在同一事務中,持鎖時間長
- 鎖升級:行鎖升級為表鎖,增加死鎖概率
2. 改造步驟
2.1 添加依賴
在 pom.xml
中添加Spring Retry相關依賴:
<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId>
</dependency>
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId>
</dependency>
2.2 創建重試配置類
@Configuration
@EnableRetry
public class RetryConfig {@Beanpublic RetryTemplate retryTemplate() {RetryTemplate retryTemplate = new RetryTemplate();// 重試策略:最多重試3次SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();retryPolicy.setMaxAttempts(3);retryTemplate.setRetryPolicy(retryPolicy);// 退避策略:指數退避,初始延遲1秒ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();backOffPolicy.setInitialInterval(1000);backOffPolicy.setMultiplier(2.0);backOffPolicy.setMaxInterval(5000);retryTemplate.setBackOffPolicy(backOffPolicy);return retryTemplate;}
}
2.3 啟用重試機制
在主應用類上添加 @EnableRetry
注解:
@EnableRetry
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}
2.4 改造核心處理邏輯
改造前(容易死鎖):
public void processData(List<DataDto> list, QueryParam param) {// 逐條處理:刪除 + 計算list.forEach(item -> {deleteRelatedData(item.getId()); // 單條刪除calculateData(item); // 單條計算});
}
改造后(兩階段處理):
public void processData(List<DataDto> list, QueryParam param) {if (CollectionUtils.isEmpty(list)) {return;}// 第一階段:范圍刪除(有重試機制)deleteDataByRange(param.getType(), param.getCategory(), param.getStartTime(), param.getEndTime(), param.getTempId());// 第二階段:批量計算(純計算,無刪除操作)for (DataDto item : list) {calculateData(item);}
}
2.5 在關鍵方法上添加重試注解
@Retryable(value = {DeadlockLoserDataAccessException.class, DataAccessException.class},maxAttempts = 3,backoff = @Backoff(delay = 1000, multiplier = 2, maxDelay = 5000)
)
public void deleteDataByRange(String type, String category, LocalDateTime startTime, LocalDateTime endTime, String tempId) {log.info("開始范圍刪除數據:type={}, category={}", type, category);dataMapper.deleteByRange(type, category, startTime, endTime, tempId);log.info("范圍刪除完成");
}@Recover
public void recoverFromDeadlock(Exception ex, String type, String category,LocalDateTime startTime, LocalDateTime endTime, String tempId) {log.error("刪除數據重試失敗,最終放棄。參數:type={}, category={}", type, category, ex);throw new BusinessException("數據刪除失敗,請稍后重試");
}
2.6 移除冗余刪除操作
檢查并移除業務流程中的冗余刪除調用:
public void businessProcess(ProcessParam param) {// 移除冗余的刪除調用// deleteRelatedData(param); // 刪除這行// 保留必要的刪除操作deleteSpecificTypeData(param);// 業務計算邏輯processBusinessLogic(param);
}
3. 關鍵改造點總結
3.1 核心改造思路
- 分離刪除和計算:避免在同一循環中進行刪除和計算
- 范圍刪除替代逐條刪除:減少數據庫操作次數和鎖競爭
- 添加重試機制:對不可避免的死鎖進行自動重試
- 清理冗余操作:移除不必要的刪除調用
3.2 改造前后對比
改造前 | 改造后 |
---|---|
逐條刪除 + 計算 | 范圍刪除 + 批量計算 |
長事務持鎖 | 短事務快速釋放鎖 |
無重試機制 | 自動重試死鎖異常 |
多處冗余刪除 | 精簡刪除操作 |
3.3 效果驗證
- 死鎖發生頻率顯著降低
- 數據處理性能提升
- 系統穩定性增強
- 無數據丟失問題
4. 注意事項
4.1 重試配置要點
- 只對特定異常類型重試(如死鎖異常)
- 設置合理的重試次數和間隔
- 必須提供
@Recover
方法處理最終失敗
4.2 兩階段處理要點
- 確保刪除和計算之間沒有其他操作干擾
- 刪除操作要支持范圍查詢
- 計算邏輯要保證冪等性
4.3 數據一致性保證
- 關鍵保存操作不能遺漏
- 事務邊界要合理設置
- 必要時使用分布式鎖