背景

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

// 订单审批通过
@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)

最新文章

  1. dynamoDb aws config aws_access_key_id aws_secret_access_key golang
  2. 基于jQuery的email suggest插件
  3. Windows操作系统优化(Windows优化大师版) - 进阶者系列 - 学习者系列文章
  4. ubuntu下安装 infer
  5. 关于Asp.Net Mvc3.0 使用KindEditor4.0 上传图片与文件
  6. 什么是J2EE,包括哪些规范!
  7. 移动平台webApp的meta标签-----神奇的功效
  8. 【LeetCode】6 - ZigZag Conversion
  9. 网易面试题:和为n连续正数序列
  10. bq24075 锂电池 充电电路分析
  11. MapReduce的流程
  12. 【C语言编程练习】5.11 猴子吃桃子问题
  13. ANSYS中的阻尼damper
  14. [HNOI2018]寻宝游戏
  15. 环境准备——之Jdk安装
  16. Mybatis3——使用学习(二)
  17. PAT 1031 Hello World for U
  18. [LeetCode&Python] Problem 896. Monotonic Array
  19. C#按制定的环境编译替换不出对应的配置项的解决措施。
  20. python packages prebuild for windows

热门文章

  1. Hadoop入门学习笔记(二)
  2. 关于『HTML5』第一弹
  3. 从零开始实现lmax-Disruptor队列(一)RingBuffer与单生产者、单消费者工作原理解析
  4. JDK1.7HashMap源码分析
  5. JAVA - 线程同步和线程调度的相关方法
  6. 在 Traefik Proxy 2.5 中使用/开发私有插件(Traefik 官方博客)
  7. 透过Redis源码探究字符串的实现
  8. SAP string 转 number 类型
  9. linux系统漏洞lynis扫描
  10. java的方法(类似与C语言函数)