业务场景: 并发扣减库存, 使用乐观锁
贴一下主要的业务代码:
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, 问题就得到了解决.
问题二:
该方法的事务隔离级别降低了之后, 就一定没问题了吗? 其实不然. 如果该事务加入了其他事物, 也就是该方法被另一个开启事务的方法调用时, 如果调用方没有进行降级, 那么子事务的降级就无效了. 所以需要注意!