Spring Boot应用连接Azure MS SQL发生死锁问题的解决求助
解决SQL Server死锁问题的方案
问题背景
- 错误信息:
com.microsoft.sqlserver.jdbc.SQLServerException: Transaction (Process ID 160) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction. - 使用JPA版本:2.7.12
- 涉及操作:
- 查询:带
@Lock(LockModeType.OPTIMISTIC_FORCE_INCREMENT)的select * from employee where employee_id=10 - 更新:
update employee set emp_status='accept' where employee_id=10
- 查询:带
- 已尝试措施:更换JPA版本、排查索引问题
- 需求:并发访问时不再出现死锁
解决方案
1. 替换乐观锁为悲观锁
OPTIMISTIC_FORCE_INCREMENT依赖版本字段实现乐观锁,并发场景下多个事务先查询再更新时,容易因锁时机冲突导致死锁。换成PESSIMISTIC_WRITE(写锁)或PESSIMISTIC_READ(读锁),在查询阶段直接获取行级锁,避免后续更新的锁竞争。
示例代码:
@Lock(LockModeType.PESSIMISTIC_WRITE) Employee findByEmployeeId(Long employeeId);
注意要保证查询和更新操作在同一个事务内执行。
2. 合并查询与更新操作,减少锁持有步骤
不要拆分查询和更新两步走,直接用JPA的@Modifying注解执行更新语句,跳过查询环节,缩短锁的持有周期。
示例代码:
@Modifying @Query("update Employee e set e.empStatus = 'accept' where e.employeeId = :id") int updateEmployeeStatus(@Param("id") Long id);
3. 缩小事务边界
确保事务只包含核心的查询/更新操作,避免在事务中加入远程调用、文件IO等耗时操作,减少锁的持有时间,降低并发冲突概率。比如:
@Transactional(propagation = Propagation.REQUIRED) public void updateStatus(Long id) { // 只放必要的更新操作,无额外冗余逻辑 employeeRepository.updateEmployeeStatus(id); }
4. 开启SQL Server快照隔离
SQL Server默认的READ COMMITTED隔离级别下,读操作会获取共享锁,容易和写锁冲突。开启READ COMMITTED SNAPSHOT后,读操作基于快照数据,不阻塞写操作,也不会被写操作阻塞,能大幅减少死锁。
执行SQL开启:
ALTER DATABASE YourDatabaseName SET READ_COMMITTED_SNAPSHOT ON;
5. 实现死锁重试机制
极端场景下死锁仍可能发生,因此需要在业务代码中添加重试逻辑。捕获SQLServerException,判断错误码为1205(死锁错误码)时,自动重试事务。
示例代码:
public void safeUpdateEmployeeStatus(Long id) { int maxRetries = 3; int retryCount = 0; while (retryCount < maxRetries) { try { updateStatus(id); // 调用带事务的更新方法 return; } catch (SQLServerException e) { if (e.getErrorCode() == 1205) { retryCount++; try { Thread.sleep(100 * retryCount); // 重试间隔递增 } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } else { throw e; } } } throw new RuntimeException("重试多次后仍无法完成更新,死锁问题未解决"); }
内容的提问来源于stack exchange,提问作者prajna parimita kheti
相关产品推荐
相关产品推荐

