惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
小众软件
小众软件
C
Check Point Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
V
Visual Studio Blog
Last Week in AI
Last Week in AI
P
Proofpoint News Feed
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
T
Tailwind CSS Blog
Recorded Future
Recorded Future
雷峰网
雷峰网
WordPress大学
WordPress大学
A
Arctic Wolf
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Project Zero
Project Zero
T
Tor Project blog
T
Threat Research - Cisco Blogs
Scott Helme
Scott Helme
Spread Privacy
Spread Privacy
G
Google Developers Blog
Security Latest
Security Latest
MongoDB | Blog
MongoDB | Blog
T
Threatpost
I
InfoQ
T
Tenable Blog
T
The Exploit Database - CXSecurity.com
S
Security Affairs
H
Hackread – Cybersecurity News, Data Breaches, AI and More
人人都是产品经理
人人都是产品经理
C
Cisco Blogs
博客园 - 三生石上(FineUI控件)
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
aimingoo的专栏
aimingoo的专栏
C
CXSECURITY Database RSS Feed - CXSecurity.com
美团技术团队
Google DeepMind News
Google DeepMind News
The Hacker News
The Hacker News
D
Docker
博客园 - 【当耐特】
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Vercel News
Vercel News
PCI Perspectives
PCI Perspectives
Microsoft Azure Blog
Microsoft Azure Blog
C
CERT Recently Published Vulnerability Notes
月光博客
月光博客
Cloudbric
Cloudbric
A
About on SuperTechFans
F
Fortinet All Blogs

Hsu Yeung 的博客

三圣花市 | Hsu Yeung 的博客 网站支持 Live Photo 图片展示 | Hsu Yeung 的博客 梦 | Hsu Yeung 的博客 周末午餐 | Hsu Yeung 的博客 张家界 | Hsu Yeung 的博客 话剧《寻她芳踪·张爱玲》 | Hsu Yeung 的博客 博客自动生成文章目录 | Hsu Yeung 的博客 灭螂行动! | Hsu Yeung 的博客 张靓颖成都演唱会 | Hsu Yeung 的博客 最近对博客做的一些微调总结 | Hsu Yeung 的博客 Windows 上安装 Lua | Hsu Yeung 的博客 网站支持展示 B 站 iframe 视频 SpringBoot 中使用 RedisTemplate 存取整数值的坑 | Hsu Yeung 的博客 SQL JOIN 中 ON 与 WHERE 的区别 值得记录的一些工作近况 | Hsu Yeung 的博客 周传雄成都演唱会 | Hsu Yeung 的博客 并行操作导致获取数据库连接超时 | Hsu Yeung 的博客 风信子 | Hsu Yeung 的博客 Linux 安装并配置 Nginx | Hsu Yeung 的博客 使用 logrotate 切割 nginx 日志 郁金香土培结果 | Hsu Yeung 的博客 MySQL LEFT JOIN 右表有多条数据但只取最新的一条 | Hsu Yeung 的博客 从零开始挑选相机 | Hsu Yeung 的博客 郁金香 | Hsu Yeung 的博客 我的第一束花 | Hsu Yeung 的博客 快乐的一周 | Hsu Yeung 的博客 重庆,来了 | Hsu Yeung 的博客 散步 | Hsu Yeung 的博客 Git 工作区、暂存区、版本库之间的关系 | Hsu Yeung 的博客 Collectors.toMap() 操作 value 为 null 的情况 AOP 记录请求参数时序列化异常问题 | Hsu Yeung 的博客 第一次参加演唱会 | Hsu Yeung 的博客 MySQL FIND_IN_SET 函数的使用 | Hsu Yeung 的博客 自动拆箱机制导致编译失败 | Hsu Yeung 的博客 最近 | Hsu Yeung 的博客 夜爬龙泉山 | Hsu Yeung 的博客 在 Windows 上安装多个版本 JDK 并切换使用的版本 安装 Windows Terminal 和 gsudo 记录 意外的奖励 | Hsu Yeung 的博客 记第一次修热水器 | Hsu Yeung 的博客 在 Windows 电脑上调试 iPhone Safari 浏览器的网页 给网站加上 RSS | Hsu Yeung 的博客 打雷了 | Hsu Yeung 的博客 豉汁蒸排骨 | Hsu Yeung 的博客 我的 2022 年 | Hsu Yeung 的博客 如何使用 Git 给代码打 tag | Hsu Yeung 的博客 记一次有趣的上班经历 | Hsu Yeung 的博客 我记得 | Hsu Yeung 的博客 Spring 新增或修改请求 header 参数 | Hsu Yeung 的博客 Spring 事务传播机制 | Hsu Yeung 的博客 在家自制“周黑鸭” | Hsu Yeung 的博客 晚餐 | Hsu Yeung 的博客 如何解决逻辑删除与数据库唯一约束冲突 | Hsu Yeung 的博客 [转]漫画赏析:Linux 内核到底长啥样 | Hsu Yeung 的博客 数据库逻辑设计之三大范式通俗理解 | Hsu Yeung 的博客 计算机中的大小端是指什么 | Hsu Yeung 的博客 MySQL 学习笔记 | Hsu Yeung 的博客 [转]《玉子市场》的人物魅力 | Hsu Yeung 的博客 Linux 安装 Redis | Hsu Yeung 的博客 Linux 安装 MySQL | Hsu Yeung 的博客 Linux 安装 JDK | Hsu Yeung 的博客 [转]Thymeleaf 表达式工具类 | Hsu Yeung 的博客 Shell 基础入门介绍 | Hsu Yeung 的博客 Linux 中的软链接和硬链接 | Hsu Yeung 的博客 MySQL 将某些字段的值排在最前或最后 | Hsu Yeung 的博客 数据结构与算法学习笔记 | Hsu Yeung 的博客 Visual Studio Code 如何编写运行 C 程序?
获取数据库锁等待超时问题 | Hsu Yeung 的博客
2024-03-21 · via Hsu Yeung 的博客

前言

最近在 QA 环境测试订单退款流程审批通过的功能的时候看见 “Lock wait timeout exceeded; try restarting transaction” 的异常,但是这个业务逻辑并不复杂,怎么会出现锁等待超时的问题呢?跟着代码梳理了下整体的方法调用链,大致逻辑如下:


  1. 查询订单退款记录数据
  2. 更新订单退款记录的一些状态、备注等字段
  3. 调用订单退款服务
    1. 调用公共退款服务
    2. 根据退款发起结果以新事务的方式更新订单退款记录

其中第 3 步的根据退款发起结果以新事务的方式更新订单退款记录这一操作调用的方法上开启了事务并且指定了事务传播机制为 REQUIRES_NEW,再看第 2 步的操作,也是在对订单退款记录做更新,且更新时已经开启了事务,这下就明白问题所在了。

案发现场

问题代码如下(适当对方法和参数做了调整、注释,只展示了复现问题必要的一些逻辑):

订单退款记录服务:

@Transactional(rollbackFor = Exception.class)
@Override
public void auditPass(WorkflowOrderRefundAuditPassReq req) {
    String processInstanceId = req.getProcessInstanceId();
    TxOrderRefund orderRefund = this.checkAndReturnOrderRefundRecord(processInstanceId);
    // ... 其他业务逻辑 ...

    // 修改订单退款记录
    // orderRefund.setXXX();
    updateById(orderRefund);

    // 调用支付退款服务
    orderPayService.refundPay(orderRefund.getOrderCode());
}

支付退款服务:

@Transactional(rollbackFor = Exception.class)
public void refundPay(String orderCode) {
    // ... 其他业务逻辑 ...
    orderRefundService.updateOneNewTran(orderRefund);
}

其中 updateOneNewTran 方法上事务注解为:

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)

破案

第 2 步更新订单退款记录时会将该条数据锁住,如果不是同一个事务其他对该条数据进行修改的操作必须要等待前一个事务结束才能获取到锁进行操作。
这里的第 3 步由于事务传播机制是 REQUIRES_NEW,因此会开启一个新的事务,而不是加入当前的事务,因此必须要等待第 2 步的操作结束将事务提交后才能成功拿到锁,但是第 2 步的操作和第 3 步操作在同一个大方法下,因此就形成了一个死循环:2 等着 3 执行完然后提交事务,3 又需要等 2 执行完才能拿到锁执行自己的事务,直到触发数据库的锁等待超时异常。

解决方案

解决方案也比较简单,只需要将第 3 步中的事务传播机制改为默认的 Requires 即可,让其加入已有事务而不是开启一个新的事务。

当然,还有其他解决方案,比如将第 2 步的订单退款记录更新操作放到第 3 步的退款操作中去,这样就不会存在两个事务竞争同一个锁的情况了。只要能破坏锁竞争的条件,都是可行的解决方案,根据实际情况选择一种合适的方案即可。

温故知新

  1. Spring 事务传播机制
  2. MySQL 学习笔记