记录偶发更新失败问题

一,代码如下

 @Transactional(rollbackFor = Exception.class)
public void updateDelivery(){
// 1.新增反馈记录
// 2.更新订单状态,及其他字段
// 3.新增变更履历
// 4.其他新增逻辑及与其他系统交互逻辑
}

二,问题

偶尔出现(概率极低)步骤2中订单更新失败,状态没有更新,最初听说只是状态这个字段没有更新成功,其实还有其他字段,是都没有更新,即没有进行更新操作。具体更新逻辑是当前状态是10,想更新成20,结果还是10。

三,猜想与分析

猜想1,执行代码时出现了异常,但是经过查看步骤1,3,4都执行成功了,直到方法最后,并没有出现异常

猜想2,业务逻辑中该状态变更不符合具体场景,由于猜想1中1,3执行成功,且经过代码分析,不存在这种问题

猜想3,更新时出现了死锁,这条数据在其他地方也在更新,且对方持有该数据锁(行锁),导致这里更新失败

具体可能出现同时更新的场景有两个

  1. 有一个job会查询状态是10的订单数据,会写入一个中间表,然后更新这条订单数据,job更新成功

  1. 并发导致,两个请求同时调用该接口,请求1想更新成10,请求2想更新成20,由于业务逻辑比较长,请求1持有该数据锁,请求2无法更新成功,可能出现了死锁

针对场景2具体再分析

实际上,两次请求应该都会更新成功,请求2会等待请求1事物结束,然后再去更新数据,也不会出现死锁,可以模拟下这种并发场景。

具体代码如下,请求1,进来,更新后休眠20s,保证事物不结束,保证持有该数据行锁(这个应该是这样),请求2进来,并没有出现异常,且状态也会正常更新。

    @ApiOperation(value = "模拟更新订单主表异常-并发1", notes = "模拟更新订单主表异常-并发1")
    @PostMapping(value = "updateDeliveryException1")
    @Transactional(rollbackFor = Exception.class)
    public PpDelivery updateDeliveryException1(@RequestBody PpDelivery ppDelivery) {
        tiExceptionLogService.insertExceptionRecord(ppDelivery, "", "模拟事物问题");
        for (int i = 0; i < 3; i++) {
            Integer result = 0;
            Boolean isException = false;
            try {
                result = ppDeliveryService.updatePpDelivery(ppDelivery);
                Thread.sleep(20000);
                if (result == 1) {
                    break;
                }
            } catch (Exception e) {
                logger.error("ylToDPSOrderStatusFeedbackImpl update exception ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue), e);
                tiExceptionLogService.insertExceptionRecord(ppDelivery, e.getMessage(), "更新订单主表失败,数据库执行新增异常");
                isException = true;
            }
            if (result == 0 && Boolean.FALSE.equals(isException)) {
                logger.error("ylToDPSOrderStatusFeedbackImp update failed ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue));
                tiExceptionLogService.insertExceptionRecord(ppDelivery, String.valueOf(result), "更新订单主表失败,数据库执行新增未返回成功");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                logger.error("ylToDPSOrderStatusFeedbackImpl interruptedException ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue));
            }
        }
        return ppDelivery;
    }
 @ApiOperation(value = "模拟更新订单主表异常-并发2", notes = "模拟更新订单主表异常-并发2")
    @PostMapping(value = "updateDeliveryException2")
    @Transactional(rollbackFor = Exception.class)
    public PpDelivery updateDeliveryException2(@RequestBody PpDelivery ppDelivery) {
        tiExceptionLogService.insertExceptionRecord(ppDelivery, "", "模拟事物问题");
        for (int i = 0; i < 3; i++) {
            Integer result = 0;
            Boolean isException = false;
            try {
                result = ppDeliveryService.updatePpDelivery(ppDelivery);
                if (result == 1) {
                    break;
                }
            } catch (Exception e) {
                logger.error("ylToDPSOrderStatusFeedbackImpl update exception ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue), e);
                tiExceptionLogService.insertExceptionRecord(ppDelivery, e.getMessage(), "更新订单主表失败,数据库执行新增异常");
                isException = true;
            }
            if (result == 0 && Boolean.FALSE.equals(isException)) {
                logger.error("ylToDPSOrderStatusFeedbackImp update failed ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue));
                tiExceptionLogService.insertExceptionRecord(ppDelivery, String.valueOf(result), "更新订单主表失败,数据库执行新增未返回成功");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                logger.error("ylToDPSOrderStatusFeedbackImpl interruptedException ppDelivery: {}", JSON.toJSONString(ppDelivery, SerializerFeature.WriteMapNullValue));
            }
        }
        return ppDelivery;
    }

通过以上分析,如果排除场景2,很可能就是场景1了。且查询更新时间job中这条数据更新与最初代码中步骤3的时间完全问好

四,解决方案

  1. 调整job执行频率,由1min改成5分钟,避免同时取到订单数据进行更新

  1. job逻辑中改循环查询更新为直接更新,减少整个方法执行时间,缩短大事物

五,疑问

  1. 查看数据库死锁日志,并没有发现出现死锁记录,show engin innodb status,如果这个信息准确,又是什么原因没有更新成功呢

  1. job中并没有声明式事物,会存在大事物问题吗,代码如下

/** 订单状态存中间表定时器
 *
*/
@JobHandler(value = "orderStatusToTiHandler")
@Component
public class OrderStatusToTiHandler extends IJobHandler {

    @Autowired
    private OrderStatusDispatchToAllService orderStatusDispatchToAllService;
    @Autowired
    private IPpDeliveryService ppDeliveryService;
    @Autowired
    private IBasCarrierService basCarrierService;
    @Autowired
    private ISysSettingService sysSettingService;
    private static final Logger logger = LoggerFactory.getLogger(OrderStatusToTiHandler.class);

    @Override
    public ReturnT<String> execute(String s){
        logger.info("订单状态存中间表定时任务开始运行运行");
        List<String> statusNotIn = Arrays.asList("66","90", "99");
        List<String> statusIn = Arrays.asList("6020", "6025", "6050", "6055", "100","6045");
        PpDelivery ppDeliveryQurey = new PpDelivery();
        ppDeliveryQurey.setInterfaceStatus("0");
        ppDeliveryQurey.setSortName("delivery_no");
        ppDeliveryQurey.setSortOrder("ASC");
        List<PpDelivery> ppDeliveries = ppDeliveryService.queryAllByValuesAndStatus(ppDeliveryQurey, statusIn.toArray(new String[statusIn.size()]), statusNotIn.toArray(new String[statusNotIn.size()]));
        if (ppDeliveries.size() <= 0) {
            logger.info("订单状态存中间表定时任务运行成功");
            return SUCCESS;
        }
        for (PpDelivery ppDelivery : ppDeliveries) {
            if(StringUtils.isNotBlank(ppDelivery.getTmBasCarrierId())){
                String receiver = this.judgeCarrierPlatform(ppDelivery.getTmBasCarrierId());
                if(StringUtils.isNotBlank(receiver)){
                    orderStatusDispatchToAllService.orderStatusFeedBackToTi(ppDelivery.getTtPpDeliveryId(), receiver+"_" + ppDelivery.getCurrentStatus());
                }
            }
        }
        logger.info("订单状态存中间表定时任务运行成功");
        return SUCCESS;
    }

3,改声明式事物为编程式事物,有用吗

如果把最初的代码中,步骤2更新逻辑抽离出来,使用编程式事物,同时把整个方法声明式事物去掉,这种改造目的是缩短大事物,但是好像又是针对并发场景下大事物问题,但是上面已经验证并发场景下,会顺序执行,等待前面事物结束

以上分析可能存在不足,欢迎大佬指正,不胜感激

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值