乐趣区

关于数据库:MySQL更新锁表超时-Lock-wait-timeout-exceeded

背景

最近在做一个订单的钉钉审批性能,钉钉审批通过之后,订单更新审核状态,而后增加一条付款,并且更新付款状态:

// 订单审批通过
@Transactional(rollbackFor = Exception.class)
    public void orderPass() {
        // 更新订单审核状态
        updateOrderAuditStatus(id);
        // 增加入库
        addPutInStorage(id);
        // 更新订单入库状态
        updateOrderStorageStatus(id);
    }

其中的 增加入库 是近程 ERP 入库,增加出库之后 更新出库状态 。因为ERP 可能因为库存有余,会 入库失败 。但此时审批流程曾经完结,不可能再发动一遍审批流程。当 增加入库失败 订单审核状态 失常更新,增加入库 更新入库状态 失败。这里的解决方案是:

拆分成两个办法,一个是更新订单审核状态,另一个增加入库和更新入库状态。增加入库和更新入库状态开启一个事务,也就是增加 嵌套事务 REQUIRES_NEW,REQUIRES_NEW示意 无论是否有事务,都会创立一个新的事务。

批改后的代码如下:

// 订单审批通过
@Transactional(rollbackFor = Exception.class)
public void orderPass() {
    // 更新订单审核状态
    updateOrderAuditStatus(id);
    try {
        // 更新出库  
        updatePutInStorage(id);
    } catch (Exception e) {System.out.println("更新出库失败");
    }

}

// 更新出库
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void updatePutInStorage(Long id) throws Exception{
    // 增加入库
    addPutInStorage(id);
    // 更新订单入库状态
    updateOrderStorageStatus(id);
    System.out.println("更新出库胜利");
}

下面讲代码拆分成 更新订单审核状态 更新入库 , 其中 更新入库 报错会被 try catch 异样捕捉,不会影响到 订单审核状态更新 。而 增加入库 更新订单入库状态 处于同一个事务下,要么同时胜利,要么同时失败。上述问题也解决了。

然而运行后果

com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

起因剖析

锁超时了,为什么会有锁呢?次要是这里增加了REQUIRES_NEW

  • 外层事务对表的更新锁住了表的行,外层事务还没有提交,就调用了内层事务updatePutInStorage, 内层事务调用了updatePutInStorage
  • updatePutInStorage须要 更新订单的入库状态 , 此时外层事务锁住了该表,所以 更新订单的入库状态 无奈更新。
  • 更新订单的入库状态 期待 更新订单的审核状态 , 而REQUIRES_NEW 又会让 更新订单的审核状态 期待 更新订单的入库状态 。造成互相期待,也就造成 死锁

解决方案

死锁:两个线程为了爱护两个不同的共享资源而应用了两个互斥锁,那么这两个互斥锁利用不当的时候,可能会造成两个线程都在期待对方开释锁,在没有外力的作用下,这些线程会始终互相期待,就没方法持续运行,这种状况就是产生了死锁。

下面锁超时起因,就是死锁的一种起因。所以须要把 更新订单审核状态 办法放在最初:

// 订单审批通过
@Transactional(rollbackFor = Exception.class)
public void orderPass() {
    
    try {
        // 更新出库  
        updatePutInStorage(id);
    } catch (Exception e) {System.out.println("更新出库失败");
    }
    // 更新订单审核状态
    updateOrderAuditStatus(id);

}

// 更新出库
@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
public void updatePutInStorage(Long id) throws Exception{
    // 增加入库
    addPutInStorage(id);
    // 更新订单入库状态
    updateOrderStorageStatus(id);
    System.out.println("更新出库胜利");
}

总结

  • 增加嵌套事务须要思考到 死锁 的问题。
  • 一个事务只有等全副办法执行结束之后才会提交事务。
  • 含有嵌套的事务的更新,须要依照雷同的程序更新,不然可能会呈现锁互相期待的状况。

参考

业务上第一次遇到 MySQL 更新锁表超时(Lock wait timeout exceeded; try restarting transaction)

退出移动版