• 记一次乐观锁并发场景下遇到的问题


    业务场景: 并发扣减库存, 使用乐观锁
    贴一下主要的业务代码:

    this.assembleStockVersion(ori, sku);
    boolean flag = productSkuRepository.updateByVersion(ori);
    while (!flag) {
        ori = productSkuRepository.selectOne(ProductSkuDto.builder().skuCode(ori.getSkuCode()).build());
        this.assembleStockVersion(ori, sku);
        flag = productSkuRepository.updateByVersion(ori);
    }
    
    // 校验库存 并且给版本号赋值
    private void assembleStockVersion(ProductSkuDto ori, ProductSkuDto sku) {
    	// 校验库存是否充足
    	if (ori.getSkuStock() + sku.getChangeStock() < 0) {
    	    throw new BaseException(ErrorEnum.STOCK_IS_NOT_ENOUGH);
    	}
    	ori.setSkuStock(ori.getSkuStock() + sku.getChangeStock());
    	ori.setOriVersion(ori.getVersion());
    	ori.setVersion(ori.getVersion() + 1);
    }
    
    // 根据版本号更新
    public boolean updateByVersion(ProductSkuDto dto) {
        // 组装更新信息
        ProductSkuEntity entity = EntityConverterUtils.convert(dto, ProductSkuEntity.class);
        this.handleUpdateInfo(entity);
    
        // 组装更新条件
        ProductSkuEntity reqEntity = ProductSkuEntity.builder().skuCode(dto.getSkuCode()).version(dto.getOriVersion()).build();
        return dao.updateByExampleSelective(entity, this.createCondition(reqEntity)) > 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    遇到的问题:
    单次执行, 没有任何问题, 模拟并发场景, 发现第一次请求执行完成, 后续请求也都进来了, 但是会在while循环里死循环. 通过debug, 发现是while循环里的查询, 查到的都是并发请求之前的数据.
    在这里插入图片描述
    也就是说第一次请求执行成功后,新的版本号, 后续请求查询不到. 后续通过sleep 也排除了主从产生的问题.
    最后锁定在了事务的隔离级别上.

    这里贴一下事务的四大隔离级别:

    • 未提交读(READ UNCOMMITTED)
      READ UNCOMMITTED 提供了事务之间最小限度的隔离。除了容易产生虚幻的读操作和不能重复的读操作外,处于这个隔离级的事务可以读到其他事务还没有提交的数据,如果这个事务使用其他事务不提交的变化作为计算的基础,然后那些未提交的变化被它们的父事务撤销,这就导致了大量的数据变化。

    • 提交读(READ COMMITTED)
      READ COMMITTED 隔离级别的安全性比 REPEATABLE READ 隔离级别的安全性要差。处于 READ COMMITTED 级别的事务可以看到其他事务对数据的修改。也就是说,在事务处理期间,如果其他事务修改了相应的表,那么同一个事务的多个 SELECT 语句可能返回不同的结果。

    • 可重复读(REPEATABLE READ)
      在可重复读在这一隔离级别上,事务不会被看成是一个序列。不过,当前正在执行事务的变化仍然不能被外部看到,也就是说,如果用户在另外一个事务中执行同条 SELECT 语句数次,结果总是相同的。(因为正在执行的事务所产生的数据变化不能被外部看到)。

    • 序列化(SERIALIZABLE)
      如果隔离级别为序列化,则用户之间通过一个接一个顺序地执行当前的事务,这种隔离级别提供了事务之间最大限度的隔离。

    mysql默认的隔离级别是REPEATABLE READ, 所以并发场景, 开启事务之后, 后续请求是读不到第一次已经更新的版本号的, 所以将隔离级别降低为READ COMMITTED, 问题就得到了解决.

    问题二:
    该方法的事务隔离级别降低了之后, 就一定没问题了吗? 其实不然. 如果该事务加入了其他事物, 也就是该方法被另一个开启事务的方法调用时, 如果调用方没有进行降级, 那么子事务的降级就无效了. 所以需要注意!

  • 相关阅读:
    SpringBoot项目中使用MultipartFile来上传文件(包含多文件)
    最好的前端框架
    《SpringBoot篇》04.超详细多环境开发介绍
    C++模版元编程(持续更新)
    python 办公自动化(Excel)
    程序员兼职社区招募(内含技术指导)
    用AI的智慧,传递感恩之心——GPT-4o助力教师节祝福
    从功能测试到掌握自动化,四个月时间我是如何从点工进入互联网大厂
    C. Cyclic Permutations(组合数学+单峰序列)
    实验(三):单片机I/O口实验-模拟开关灯
  • 原文地址:https://blog.csdn.net/Best_Lynn/article/details/127413001