当前位置: 首页 > news >正文

记录一次生产环境数据库死锁的处理过程

文章目录

    • 一. 项目背景
    • 二. 分析过程
      • 2.1 定位错误日志
      • 2.2 初步分析与子任务背景介绍
      • 2.2 那么是不是发生并发了呢?
        • 2.2.1 业务日志分析
        • 2.2.1 死锁分析
      • 2.3 为什么发生了并发?
    • 三. 处理
    • 四. 后端服务优化
    • 五. 其他
      • 5.1 为什么插入排序不能100%避免批量插入的死锁? 待补充

一. 项目背景

1. 负责某个项目的生产环境出现了数据库死锁的问题。该项目是springboot单体项目,集成了quartz调度定时任务。国庆节第一天收到反馈,我的一个售后问题导入任务一直处于初始状态,部门老板很重视,所以下面记录下处理过程。在这里说下该定时任务的具体情况, 该售后问题导入的任务是拆分成了5个子任务。主任务是tb_problem_num_import_record 记录的是导入文件的信息(存储在阿里云),第一个子任务将明细从阿里云读取出来,匹配公司其他业务的信息,然后存储到tb_problem_num_import_detail, 再修改tb_problem_num_import_record的状态。2.  供职在一家小公司,部分操作不是那么规范,而这是问题的核心。

二. 分析过程

一切从日志开始

2.1 定位错误日志

分析日志之后,发现了2种不同的报错1.  ERROR c.h.r.s.i.ProblemCronServiceImpl - [updateRecordWithOptimisticLock,742] - 创建问题导入明细  导入记录ID:43 并发导致回滚,回滚 请下次继续。触发点是乐观锁更新tb_problem_num_import_record时,发现版本是旧版本时,主动抛出异常,以触发事务回滚。

在这里插入图片描述

 3. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction; Deadlock found when trying to get lock; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction 记录ID:43触发点是批量插入tb_problem_num_import_detail死锁导致的。

在这里插入图片描述
在这里插入图片描述

2.2 初步分析与子任务背景介绍

 两种错误日志都在表名该任务的执行发生了并发。那么当前任务严格限制并发呢?1. tb_problem_num_import_detail问题明细要用来生成日月年级别的进线率的问题数, 反馈率的问题数,任务重,执行流程长, 涉及到到多张表。 在这个过程中可能被一些更新,删除问题明细/更新删除售后问题分类的操作干扰,操作多个子任务执行结果不一致(比如:日与年不一致) 所以在整个生命周期是有乐观锁做版本控制,保证数据的一致性。2. 该定时子任务使用事务保证tb_problem_num_import_record,tb_problem_num_import_detail的数据一致性,而tb_problem_num_import_detail的format_date索引是5个字段组成的唯一键,在2个事务大量写入数据时 会产生死锁。

2.2 那么是不是发生并发了呢?

2.2.1 业务日志分析
从子任务开发的日志以及终结日志可以看到相邻任务的生命周期没有在交叉,不存在并发。
那这个其实完全跟错误日志显示内容冲突的。

在这里插入图片描述

2.2.1 死锁分析
1. 死锁诊断 SHOW ENGINE INNODB STATUS;
2025-10-07 14:22:01 140289037563648
*** (1) TRANSACTION:
TRANSACTION 97979832, ACTIVE 1 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 1 row lock(s), undo log entries 1
MySQL thread id 7160065, OS thread handle 140288177653504, query id 461941933 172.24.106.186 ops_user update*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 6350 page no 35749 n bits 176 index format_date of table `hnjl`.`tb_problem_num_import_detail` trx id 97979832 lock mode S waiting
Record lock, heap no 68 PHYSICAL RECORD: n_fields 6; compact format; info bits 00: len 8; hex 3230323530393234; asc 20250924;;1: len 15; hex e58585e4b88de8bf9be58ebbe794b5; asc                ;;2: len 19; hex 36393430353330353236353339353532363737; asc 6940530526539552677;;3: len 19; hex 4b5843512d323632332d542d7863712d6c616e; asc KXCQ-2623-T-xcq-lan;;4: len 1; hex 81; asc  ;;5: len 8; hex 800000000013540a; asc       T ;;*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 6350 page no 35749 n bits 176 index format_date of table `hnjl`.`tb_problem_num_import_detail` trx id 97979832 lock mode S waiting
Record lock, heap no 68 PHYSICAL RECORD: n_fields 6; compact format; info bits 00: len 8; hex 3230323530393234; asc 20250924;;1: len 15; hex e58585e4b88de8bf9be58ebbe794b5; asc                ;;2: len 19; hex 36393430353330353236353339353532363737; asc 6940530526539552677;;3: len 19; hex 4b5843512d323632332d542d7863712d6c616e; asc KXCQ-2623-T-xcq-lan;;4: len 1; hex 81; asc  ;;5: len 8; hex 800000000013540a; asc       T ;;*** (2) TRANSACTION:
TRANSACTION 97979827, ACTIVE 1 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1128, 2 row lock(s), undo log entries 1783
MySQL thread id 7157071, OS thread handle 140287595083520, query id 461941991 172.24.106.180 ops_user update
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 6350 page no 35749 n bits 176 index format_date of table `hnjl`.`tb_problem_num_import_detail` trx id 97979827 lock_mode X locks rec but not gap
Record lock, heap no 68 PHYSICAL RECORD: n_fields 6; compact format; info bits 00: len 8; hex 3230323530393234; asc 20250924;;1: len 15; hex e58585e4b88de8bf9be58ebbe794b5; asc                ;;2: len 19; hex 36393430353330353236353339353532363737; asc 6940530526539552677;;3: len 19; hex 4b5843512d323632332d542d7863712d6c616e; asc KXCQ-2623-T-xcq-lan;;4: len 1; hex 81; asc  ;;5: len 8; hex 800000000013540a; asc       T ;;*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 6350 page no 35749 n bits 176 index format_date of table `hnjl`.`tb_problem_num_import_detail` trx id 97979827 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 68 PHYSICAL RECORD: n_fields 6; compact format; info bits 00: len 8; hex 3230323530393234; asc 20250924;;1: len 15; hex e58585e4b88de8bf9be58ebbe794b5; asc                ;;2: len 19; hex 36393430353330353236353339353532363737; asc 6940530526539552677;;3: len 19; hex 4b5843512d323632332d542d7863712d6c616e; asc KXCQ-2623-T-xcq-lan;;4: len 1; hex 81; asc  ;;5: len 8; hex 800000000013540a; asc       T ;;*** WE ROLL BACK TRANSACTION (1)
从上述信息可以看出
2. 涉及到2个事务事务ID: 97979832  IP:  172.24.106.186    事务ID: 97979827  IP:  172.24.106.180    
3. 这两个事务存在死锁
事务持有锁等待锁
97979832间隙锁记录68的S锁
97979827记录68的排它锁等待间隙锁
4.  可能得执行流程如下

在这里插入图片描述

综上:

  1. 有2个实例在同时跑这个定时任务, ip分别是172.24.106.186,172.24.106.180, 是存在并发的。 (根据ip去服务器确认,果然是有2台是实例在运行)
  2. 业务日志之所以显示没有发生并发是因为业务日志是从一个后端实例上收集的。

2.3 为什么发生了并发?

公司这个项目是明确的单实例服务,为什么会出现2个后端服务呢?
跟运维同事确认后,发现运维同事之前搞了双实例的负载均衡,但是遇到了问题,所以放弃了负载均衡,但是没有停掉另外一个后端实例。

三. 处理

1. 关掉多出来的实例
2. 激活执行失败的任务

四. 后端服务优化

  1. 插入排序优化
    tb_problem_num_import_detail明细列表在批量插入之前,按照唯一索引排序,使各个事务获取锁的顺序保持一致,规避循环等待。
    private void batchSaveImportDetails(List<TbProblemNumImportDetail> detailList, TbProblemNumImportRecord earliestImportRecord) {// 1.0 对detailList按照唯一索引排序,减少死锁的概率detailList.sort(buildDetailComparator());// 2.0 分小批插入减少,死锁的概率StopWatch stopWatch = new StopWatch();int batch = 0;for (List<TbProblemNumImportDetail> partList : CollectionUtils.partition(detailList, MYSQL_INSERT_BATCH_SIZE)) {batch++;stopWatch.start("批量插入明细记录 批次:" + batch);tbProblemNumImportDetailMapper.batchInsert(partList);stopWatch.stop();log.info("创建问题导入明细  导入记录ID:{} 批次:{} 批量插入耗时:{}", earliestImportRecord.getId(), batch, stopWatch.getLastTaskTimeMillis());}log.info("创建问题导入明细  导入记录ID:{}  导入明细数量:{} 批量插入总耗时:{}", earliestImportRecord.getId(), detailList.size(), stopWatch.getTotalTimeMillis());}/*** 构建比较器* @return*/private Comparator<TbProblemNumImportDetail> buildDetailComparator() {return Comparator.comparing(TbProblemNumImportDetail::getFormatDate, Comparator.nullsLast(String::compareTo)).thenComparing(TbProblemNumImportDetail::getProblemName, Comparator.nullsLast(String::compareTo)).thenComparing(TbProblemNumImportDetail::getOrderNo, Comparator.nullsLast(String::compareTo)).thenComparing(TbProblemNumImportDetail::getSpecNo, Comparator.nullsLast(String::compareTo)).thenComparing(TbProblemNumImportDetail::getUniqFlag, Comparator.nullsLast(Integer::compareTo));}
  1. 分布式锁

加分布式锁
有点难绷: 公司不给批钱买redis, 需求非常的赶996模式下也功夫自己搭redis服务供各个服务使用。

五. 其他

5.1 为什么插入排序不能100%避免批量插入的死锁? 待补充

http://www.dtcms.com/a/465327.html

相关文章:

  • 首先确定网站建设的功能定位seo推广工具
  • Nestjs框架: Pino 与 Pino-Elasticsearch 组合实现高性能日志写入与检索的完整方案
  • 走近实验技术中的“四大发明”之Southern blot、Northern blot和Western blot
  • 网站建设需求文档模板下载想做电商从哪里入手
  • ai做网站建站做得好的公司
  • 网络层--数据链路层
  • 网站设计应该遵循哪些原则手机网站打开手机app
  • 【AI安全】Qwen3Guard: 实时流式检测实现AI模型安全防护新标杆
  • 网络攻防技术:网络安全攻击概述
  • 【开题答辩全过程】以 “有客”旅游小助手平台为例,包含答辩的问题和答案
  • 如何创建网站难吗wordpress创建公告
  • 探索MySQL存储过程的性能优化技巧与最佳实践
  • UNIX下C语言编程与实践62-UNIX UDP 编程:socket、bind、sendto、recvfrom 函数的使用
  • UNIX下C语言编程与实践64-UNIX 并发 Socket 编程:I/O 多路复用 select 函数与并发处理
  • 世界杯哪个网站做代理跨境电商网站系统开发
  • SNK施努卡CCD视觉检测系统
  • 杨和勒流网站建设网站建设制作设计
  • SQLite架构
  • 初识Linux和Linux基础指令详细解析及shell的运行原理
  • Python容器内存三要素
  • NumPy 矩阵库(numpy.matlib)用法与作用详解
  • Web 开发 26
  • 正规app软件开发费用漯河网站优化
  • 人工智能学习:线性模型,损失函数,过拟合与欠拟合
  • 开篇词:为何要懂攻防?—— 实战化安全思维的建立
  • 怎么在qq上自己做网站wordpress是一款强大的
  • 网站建设公司 成本结转ppt之家模板免费下载
  • Android Vibrator学习记录
  • pop、push、unshift、shift的作用?
  • 大模型激活值相关公式说明(114)