You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 13:55:13