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

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

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;
}

遇到的问题:
单次执行, 没有任何问题, 模拟并发场景, 发现第一次请求执行完成, 后续请求也都进来了, 但是会在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, 问题就得到了解决.

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值