Spring Boot 3+SQL Server悲观锁跨线程未触发预期异常求助
排查建议:Spring Boot 3 + SQL Server 未触发PessimisticLockException
问题背景
使用Spring Boot 3搭配SQL Server,数据库隔离级别为Read Committed。测试逻辑中预期主线程会触发PessimisticLockException,但实际未出现该异常,需排查原因。
测试代码整理
控制器代码
callTest(); return new ResponseEntity<>(HttpStatus.OK); } @Transactional public void callTest() throws InterruptedException { Cart cart = new Cart(); // 设置实体属性 cart = cartRepository.save(cart); // 创建CountDownLatch,计数为1 CountDownLatch latch = new CountDownLatch(1); Cart finalCart = cart; Thread t = new Thread(() -> { try { logger.info("Updated from spwan thread"); updateCArt(finalCart.getCartID(), "New Name"); latch.countDown(); // 线程完成后减少计数 Thread.sleep(1900L); } catch (PessimisticLockException e) { // 忽略异常 } catch (InterruptedException e) { throw new RuntimeException(e); } }); t.start(); // 等待latch计数归0 latch.await(); try { logger.info("Updated from main thread"); updateCArt(cart.getCartID(), "Another New Name"); } catch (PessimisticLockException e) { logger.info("Exception in main thread ", e); // 预期触发此异常 } logger.info("Completed succesfully"); } public Cart updateCArt(int id, String newName) { logger.info("Entering updateCart method "); List<Cart> cart = cartRepository.getCarts("678999", 2L); cart.get(0).setStatus(1); return cartRepository.save(cart.get(0)); }
Repository查询方法代码
@Lock(LockModeType.PESSIMISTIC_WRITE) @Transactional @Query(value = "SELECT c FROM Cart c ") List<Cart> getCarts(String param1, Long param2);
具体排查点
1. 子线程事务提前释放锁
子线程调用的updateCArt无事务注解,getCarts的@Transactional默认传播行为是PROPAGATION_REQUIRED,会开启独立事务。但updateCArt执行完成后,该事务立即提交,锁随之释放,主线程执行时锁已不存在。
- 验证/修正:将
updateCArt添加@Transactional注解,并把latch.countDown()移到Thread.sleep(1900L)之后,确保子线程在主线程执行更新时仍持有锁。
2. 悲观锁未锁定目标数据
getCarts的查询语句无过滤条件,会锁定所有Cart记录,但你实际要操作的是指定cartID的记录,可能锁的范围不匹配,导致主线程操作的记录未被锁定。
- 修正:修改查询语句为
SELECT c FROM Cart c WHERE c.cartID = :id,精准锁定目标记录。
3. 未设置悲观锁超时时间
SQL Server默认锁超时时间较长,JPA默认悲观锁超时为无限等待(-1),主线程会一直等待锁释放而非抛出异常。
- 配置:在
application.properties中添加spring.jpa.properties.javax.persistence.lock.timeout=500(单位毫秒),设置超时时间触发异常。
4. 检查SQL语句是否生成正确锁语法
确认@Lock(LockModeType.PESSIMISTIC_WRITE)是否正确生成SQL Server的悲观锁语句(WITH (UPDLOCK, HOLDLOCK))。
- 验证:开启SQL日志,添加以下配置查看执行语句:
logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
5. 数据库索引与锁升级问题
若cartID字段无索引,SQL Server可能将行锁升级为表锁,或因扫描全表导致锁行为不符合预期。
- 验证:检查
cartID是否有索引;通过SQL Server的sys.dm_tran_locks视图查看锁的持有情况,确认子线程是否持有目标记录的锁。
6. 主线程事务传播行为
callTest方法带有@Transactional,主线程调用updateCArt会加入当前事务,需确认子线程事务在主线程执行更新时是否仍未提交。
- 验证:调整子线程的执行顺序,确保锁在主线程执行更新时处于持有状态。
内容的提问来源于stack exchange,提问作者darthwader
相关产品推荐
相关产品推荐

