针对mysql数据库并发情况下的库存操作
  xJoHYt33S7Q3 2023年12月09日 28 0

最近在做一个wms仓储管理系统,需求是入库时候,如果仓库有这个商品就更改库存,没有这个商品的话就需要加上这个商品的的库存记录。

乐观锁

看了之前项目写的对库存的操作是先查询库存表是否存在这个商品,如果不存在就新增,如果存在的话就修改,此时修改这里加上乐观锁,这里只对修改库存时候加了锁,所以如果这个商品不存在,可能会出现重复插入的问题。 附上代码片段:

final LambdaQueryWrapper<MaterialInventory> materialQuery = Wrappers.lambdaQuery(MaterialInventory.class);
        materialQuery.eq(MaterialInventory::getTenantId, currentUser.getTenantId());
        materialQuery.eq(MaterialInventory::getWarehouseId, materialInventoryUpdateDTO.getWarehouseId());
        materialQuery.eq(MaterialInventory::getSkuId, materialInventoryUpdateDTO.getSkuId());
        materialQuery.eq(MaterialInventory::getProperties, materialInventoryUpdateDTO.getProperties());
        final MaterialInventory materialInventory = this.baseMapper.selectOne(materialQuery);
        if (materialInventory == null) {
            // 新增库存
            final MaterialInventoryAddDTO materialInventoryAddDTO = returnMaterialInventory(materialInventoryUpdateDTO);
            materialInventoryServiceTx.doAddMaterialInventory(materialInventoryAddDTO);
        } else {
            // 修改库存
            materialInventoryServiceTx.doUpdateMaterialInventory(materialInventory.getId(), currentUser.getTenantId(), materialInventoryUpdateDTO);
        }
@Transactional(rollbackFor = Exception.class)
    public void doUpdateMaterialInventory(long inventoryId, long tenantId, MaterialInventoryUpdateDTO updateDTO) {
        int i = 0;
        boolean isSuccess = true;
        // 进入乐观锁控制循环,执行5次,
        do {
            i++;
            if (!isSuccess) {
                log.info("乐观锁拦截本次更新,强迫重新执行");
                if (i > CommonConstant.VERSION_LOCKER_EXEC_TIMES) {
                    throw BusinessException.createBusinessException(CommonErrorCode.E_220001, "更新库存时出现并发异常,请稍后重试");
                }
            }
            final LambdaQueryWrapper<MaterialInventory> inventoryQuery = Wrappers.lambdaQuery(MaterialInventory.class);
            inventoryQuery.eq(MaterialInventory::getTenantId, tenantId);
            inventoryQuery.eq(MaterialInventory::getId, inventoryId);
            inventoryQuery.eq(MaterialInventory::getWarehouseId, updateDTO.getWarehouseId());
            inventoryQuery.eq(MaterialInventory::getProperties, updateDTO.getProperties());
            final MaterialInventory materialInventory = materialInventoryService.getOne(inventoryQuery);
            materialInventory.setQuantity(materialInventory.getQuantity().add(updateDTO.getQuantity()));
            materialInventory.setQuantityAssistant(materialInventory.getQuantityAssistant().add(updateDTO.getQuantityAssistant()));
            materialInventory.setWaitingInQuantity(materialInventory.getWaitingInQuantity().add(updateDTO.getWaitingInQuantity()));
            materialInventory.setWaitingInQuantityAssistant(materialInventory.getWaitingInQuantityAssistant().add(updateDTO.getWaitingInQuantityAssistant()));
            materialInventory.setWaitingOutQuantity(materialInventory.getWaitingOutQuantity().add(updateDTO.getWaitingOutQuantity()));
            materialInventory.setWaitingOutQuantityAssistant(materialInventory.getWaitingOutQuantityAssistant().add(updateDTO.getWaitingOutQuantityAssistant()));
            materialInventory.setUpdateBy(userService.getCurrentUser().getId());
            materialInventory.setTenantId(tenantId);
            if (updateDTO.getQuantity().compareTo(BigDecimal.ZERO) > 0 && updateDTO.getPrice().compareTo(materialInventory.getPrice()) != 0) {
                BigDecimal price;
                BigDecimal amount;
                // 计算平均价格
                if (materialInventory.getQuantity().add(updateDTO.getQuantity()).compareTo(BigDecimal.ZERO) != 0) {
                    amount = materialInventory.getQuantity().multiply(materialInventory.getPrice()).add(
                            updateDTO.getQuantity().multiply(updateDTO.getPrice()));
                    price = amount.divide(materialInventory.getQuantity().add(updateDTO.getQuantity()), 4, RoundingMode.HALF_UP);
                    materialInventory.setPrice(price);
                }
            }
            isSuccess = materialInventoryService.updateById(materialInventory);
        } while (!isSuccess);
    }

悲观锁

现在项目我使用的是mysql的悲观锁,select……for update,因为mysql默认隔离级别是可重复读,针对索引查询时候可能会出现间隙锁,造成死锁等问题,所以此处先把mysql隔离级别改成了读已提交,然后把仓库id+商品id加了个联合唯一索引

  • 1.先查询表里是否存在仓库id+商品id的记录(使用 select……for update)
  • 2.存在的话更新即可
  • 3.不存在的话插入,放到try catch里然后在catch捕获一个唯一键冲突异常,因为加了联合唯一索引,所以并发可能会重复插入报错。并在catch里再次执行步骤1,然后更改库存。 附上代码片段:(因为我的库存表还有一个子库存表,所以finally里执行了一个向子库存表插入的逻辑,只要保证了主表的唯一性,子表也肯定会唯一,所以子表无需加锁)
// 基于RC隔离级别
    public void updateInventory(InventoryDTO inventoryDTO) {
        Inventory inventory = this.baseMapper.selectInventoryLock(inventoryDTO.getWarehouseId(), inventoryDTO.getSkuId());
        Inventory newInventory = null;
        Inventory newInventoryEntry = null;
        if (inventory == null) {
            // 不存在插入
            Inventory inventoryEntry = new Inventory();
            BeanUtils.copyProperties(inventoryDTO, inventoryEntry);
            try {
                this.baseMapper.insert(inventoryEntry);
                newInventoryEntry = inventoryEntry;
            } catch (DuplicateKeyException e) {
                // todo日志
                // 唯一键冲突
                Inventory inventorySecond = this.baseMapper.selectInventoryLock(inventoryDTO.getWarehouseId(), inventoryDTO.getSkuId());
                inventorySecond.setQuantity(inventorySecond.getQuantity().add(inventoryDTO.getQuantity()));
                inventorySecond.setPackageQty(inventorySecond.getQuantity().divide(new BigDecimal(inventorySecond.getItemQty()), 2, RoundingMode.HALF_UP));
                this.updateById(inventorySecond);
                newInventory = inventorySecond;
            } finally {
                if (newInventory == null) {
                    // 代表插入成功
                    if (newInventoryEntry == null) {
                        throw new JeecgBootException("插入inventory表失败");
                    }
                    if (inventoryDTO.getUseUniCode().equals(CommonYesNo.YES)) {
                        List<InventoryUniCodeDTO> uniCodes = inventoryDTO.getUniCodes();
                        for (InventoryUniCodeDTO inventoryUniCodeDTO : uniCodes) {
                            InventoryUniCode inventoryUniCode = new InventoryUniCode();
                            inventoryUniCode.setInventoryId(newInventoryEntry.getId());
                            inventoryUniCode.setWarehouseId(newInventoryEntry.getWarehouseId());
                            inventoryUniCode.setMaterialCode(newInventoryEntry.getMaterialCode());
                            inventoryUniCode.setBatch(newInventoryEntry.getBatch());
                            inventoryUniCode.setUniCode(inventoryUniCodeDTO.getUnicode());
                            inventoryUniCode.setItemQty(newInventoryEntry.getItemQty());
                            inventoryUniCode.setQuantity(inventoryUniCodeDTO.getQuantity());
                            inventoryUniCodeMapper.insert(inventoryUniCode);
                        }
                    }
                } else {
                    // 代表catch已经执行
                    if (inventoryDTO.getUseUniCode().equals(CommonYesNo.YES)) {
                        updateDetailQuantity(inventoryDTO, newInventory);
                    }
                }
            }
        } else {
            // 存在更新
            inventory.setQuantity(inventory.getQuantity().add(inventoryDTO.getQuantity()));
            inventory.setPackageQty(inventory.getQuantity().divide(new BigDecimal(inventory.getItemQty()), 2, RoundingMode.HALF_UP));
            this.updateById(inventory);
            if (inventoryDTO.getUseUniCode().equals(CommonYesNo.YES)) {
                updateDetailQuantity(inventoryDTO, inventory);
            }
        }
    }
public void updateDetailQuantity(InventoryDTO inventoryDTO, Inventory newInventory) {
        for (InventoryUniCodeDTO inventoryUniCodeDTO : inventoryDTO.getUniCodes()) {
            LambdaQueryWrapper<InventoryUniCode> lambdaQueryWrapper = Wrappers.lambdaQuery();
            lambdaQueryWrapper.eq(InventoryUniCode::getInventoryId, newInventory.getId());
            lambdaQueryWrapper.eq(InventoryUniCode::getUniCode, inventoryUniCodeDTO.getUnicode());
            InventoryUniCode inventoryUniCode = inventoryUniCodeMapper.selectOne(lambdaQueryWrapper);
            if (inventoryUniCode == null) {
                // 明细是空
                InventoryUniCode inventoryUniCode1 = new InventoryUniCode();
                inventoryUniCode1.setInventoryId(newInventory.getId());
                inventoryUniCode1.setWarehouseId(newInventory.getWarehouseId());
                inventoryUniCode1.setMaterialCode(newInventory.getMaterialCode());
                inventoryUniCode1.setBatch(newInventory.getBatch());
                inventoryUniCode1.setUniCode(inventoryUniCodeDTO.getUnicode());
                inventoryUniCode1.setItemQty(newInventory.getItemQty());
                inventoryUniCode1.setQuantity(inventoryUniCodeDTO.getQuantity());
                inventoryUniCodeMapper.insert(inventoryUniCode1);
            } else {
                // 明细不是空
                inventoryUniCode.setQuantity(inventoryUniCode.getQuantity().add(inventoryUniCodeDTO.getQuantity()));
                inventoryUniCodeMapper.updateById(inventoryUniCode);
            }
        }
    }
【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年12月09日 0

暂无评论

推荐阅读
xJoHYt33S7Q3