排查生产环境:MySQLTransactionRollbackException数据库死锁

一. 问题现状

程序直接宕机,并在error.log日志中发现大量的报错日志,如下:

### Error updating database.  
Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: 
Lock wait timeout exceeded; try restarting transaction
### The error may exist in com/xxx/dao/mapper/xxxMapper.java (best guess)
### The error may involve com.xxx.dao.mapper.xxxMapper.update-Inline
### SQL: UPDATE xxx_table SET a1=?, a2=?, a3=?, a4=?, a5=?  WHERE (q1= ?)

程序中使用的是mybatis-plus,报错信息准确提示出了对应的mapper文件以及对应的sql操作是updateById语句。再结合其他日志信息,初步判断出了对应的位置以及症结可能与事务操作有关。

二. 可能原因
事务过程中执行其他非数据库操作,导致事务长期未被处理
事务未被正常处理
网段导致应用端请求未被正常发送给数据库,数据库等待应用后续操作
应用服务器性能问题,CPU爆满等导致应用无法及时切换到该进程进行处理
根据以上原因,首先排查服务器情况发现不存在cpu爆满的情况,后续继续追究MySQL事务方面的问题。
从MySQL入手,执行以下语句查看锁情况:

-- 查看正在锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS; 
-- 查看等待锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

(由于笔者的线上环境执行了重启,所以这会儿已经看不到具体结果了,实际上应该能看到对应等待锁的事务)
但是可以模拟线上情况,比如在代码中的@Transaction代码中打个debug断点,然后观察情况:

-- 查看执行进程
SHOW PROCESSLIST;
-- 执行查询未提交事务语句
SELECT * from  information_schema.INNODB_TRX;

基本可以确定问题出现在代码层。

三. 排查代码

生产伪代码如下:

@Transactional(rollbackFor = Exception.class)
public synchronized boolean doSomething(Object o){// ...if(flag){aMapper.updateById(o);}// ...bMapper.insert(o);return true;}

这里的目的是做一个事务,aMapper需要根据某个条件判断更新操作,bMapper则需要根据业务流程做新增操作,同时为了考虑并发情况下的操作所以在方法名上加了synchronized 同步锁。咋一看感觉没什么问题,但是在service层的方法上同时使用事务和锁无法保证同步。

原理是:@Transactional是使用了Sping AOP 实现的;Synchronized只是锁当前代码块,当执行完Synchronized包含的代码块就已经执行完了;此时@Transactional还未提交,所以就造成上一个事务没有提交,而下一个请求过来了争夺数据库的事务锁,在大量请求的情况下,有可能会承受不住。

针对这种情况,我们得保证让Synchronized的锁范围比@Transactional作用范围大,于是可以做如下改造:
 

public synchronized boolean doSomething(Object o){return transactionDoSomething(o)
}@Transactional(rollbackFor = Exception.class)
public boolean transactionDoSomething(Object o){// ...if(flag){aMapper.updateById(o);}// ...bMapper.insert(o);
}

另外一种方案:


@Transactional(rollbackFor = Exception.class)
public boolean transactionDoSomething(Object o){synchronized(this){// doSomethingreturn true;}
}

三. 扩展(ReentrantLock/druid自动重连数据库)
3.1 根因分析
经过以上处理,实际上应该不会再出现数据库死锁的情况了,但是经过日志的进一步查看发现没有将@Transaction与synchronized一起使用的地方也出现过死锁情况,在此处仅仅使用了synchronized同步锁,唯一与以上情况相似的就是同样使用了updateById。
 

public synchronized void doSomething(Object o){// ...xxxMapper.updateById(o);
}

剩下就是还有一种可能,网络波动、防火墙或是其他原因,导致了应用和mysql的连接断开,这个时候进入到这个代码块的执行逻辑在使用已经失败的连接,又由于这是加了synchronized的同步代码块,第一个进来的请求一直在等待连接导致后续进来的请求全都被堵塞。

3.2 改造synchronized使用ReentrantLock
于是我们在此处尽量引入一种比较友好的锁,ReentrantLock。
ReentrantLock的tryLock方法支持配置最大同步等待时间,由此来避免以上堵塞情况,写法如下:
 

@Service
public class LockService{// 初始化ReentrantLockprivate final Lock reentrantLock = new ReentrantLock();// 设置最大锁等待时间private final long tryLockTime = 2L;public void addRound(Long manMachineId,LocalDateTime time) {try {if (reentrantLock.tryLock(tryLockTime, TimeUnit.SECONDS)) {System.out.println("获取到锁,开始做一些事...");}else {System.out.println("等待2s后获取锁失败");}} catch (InterruptedException e) {e.printStackTrace();} finally {// 最终需要释放锁reentrantLock.unlock();}}
}

3.3 加入druid自动重连数据库

程序中使用了druid,所以我们加入以下配置信息即可实现在数据库停止的时候等待数据库恢复并自动重连,配置如下:

# 标记当Statement或连接被泄露时是否打印程序的stack traces日志
spring.datasource.druid.log-abandoned=true
# 是否自动回收超时连接
spring.datasource.druid.remove-abandoned=true
# 超时时间(以秒数为单位)
spring.datasource.druid.remove-abandoned-timeout=10

四. 总结
至此,本次MySQL线上死锁问题就已结束排查。
由于线上问题一般都比较复杂或者比较难复现,所以排查线上问题首先需要分析日志,这个时候就要求我们程序的日志要尽可能做到完善。
然后就是大胆猜测,小心验证,其中不免会经历多次推到重来的历程。此后该问题再次出现就不会再成为你的问题。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/159225.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ Boost 实现异步端口扫描器

端口扫描是一种用于识别目标系统上哪些网络端口处于开放、关闭或监听状态的网络活动。在计算机网络中,端口是一个虚拟的通信端点,用于在计算机之间传输数据。每个端口都关联着特定类型的网络服务或应用程序。端口扫描通常是网络管理员、安全专业人员用来…

springboot项目中获取业务功能的导入数据模板文件

场景: 在实际业务场景中,经常会遇到某些管理功能需要数据导入共功能,但既然是导入数据,肯定会有规则限制,有规则就会有数据模板,但这个模板一般是让客户自己下载固定规则模板,而不是让客户自己随便上传模板。下面介绍直接下载模板 一、下载模板示例 1、在项目的…

MySQL 中 DELETE 语句中可以使用别名么?

某天,正按照业务的要求删除不需要的数据,在执行 DELETE 语句时,竟然出现了报错! 作者:林靖华,开源数据库技术爱好者,擅长MySQL和Redis的运维 爱可生开源社区出品,原创内容未经授权不…

Mysql查看Binlog文件

前期准备 检查是否开启binlog mysql> SHOW VARIABLES LIKE log_bin; // 或者 mysql> SHOW VARIABLES LIKE log%;ON代表开启,OFF代表关闭。如为OFF需 开启 后才能查看,但只能查看开启之后时间点的。 查看binlog文件有哪些 一般yum安装的mysql…

【python学习】基础篇-常用第三方库-chardet:检测文本文件的编码格式

chardet是一个Python库,用于检测文本文件的编码格式。 以下是一些基本的用法: 使用chardet.detect()函数检测文件编码 with open(example.txt, rb) as f:result chardet.detect(f.read()) print(result[encoding])使用chardet.detect()函数检测字节串…

【python学习】基础篇-常用第三方库-requests库:用于发送各种类型的HTTP请求

在Python中,requests库是一个常用的HTTP请求库,用于发送各种类型的HTTP请求。 以下是一些基本的用法: 更多高级功能可以参考官方文档:https://docs.python-requests.org/ 发送GET请求 response requests.get(https://www.examp…

Program Header Table(转载)

程序头表与段表相互独立,由ELF文件头统一管理。 程序头表负责ELF文件从文件到加载后映像的映射关系,一般只有可执行文件包含。 1. segment和section segment: 程序头表项描述的对象称为segment,即elf文件加载后的数据块; 它提供…

exports和module.exports 区别

在 Node.js 中,“exports”和“module.exports”两者的区别有以下几点: 导出对象类型不同: exports 是对 module.exports 的一个全局引用,而实际导出的是 module.exports 对象。即 exports 只是为了方便,可以在不断开…

C++学习(2):分配器allocator

new和operator new new是关键字,new 操作符的执行过程: 调用operator new分配内存;调用构造函数在operator new返回的内存地址处生成类对象; operator new是一个函数,可以被重载,通过重载它,…

微服务开发中,使用AOP和自定义注解实现对权限的校验

一、背景 微服务开发中,暴露在外网的接口,为了访问的安全,都是需要在http请求中传入登录时颁发的token。这时候,我们需要有专门用来做校验token并解析用户信息的服务。如下图所示,http请求先经过api网关,网…

[点云分割] 欧式距离分割

效果&#xff1a; 代码&#xff1a; #include <iostream> #include <chrono>#include <pcl/ModelCoefficients.h> // 模型系数的定义 #include <pcl/io/pcd_io.h> #include <pcl/point_types.h> // 各种点云数据类型 #include <pcl/sample_c…

java“贪吃蛇”小游戏

基于java实现贪吃蛇小游戏&#xff0c;主要通过绘制不同的图片并以一定速度一帧一帧地在窗体上进行展示。 我是在javaSwing项目下创建了一个包 名字叫做&#xff1a;Snakes包 包下有一个启动类和一个设置代码的主界面两个类 代码主界面&#xff1a; 代码主界面主要讲解的是 …

window文件夹下python脚本实现批量删除无法预览的图片

你是否遇到过下载的图片会发现有些图片会无法预览情况&#xff1f; 有几种原因可能导致一些图片在预览时无法正常显示&#xff1a; 损坏的图片文件&#xff1a; 图片文件可能损坏或者部分损坏&#xff0c;导致无法被正常解析和预览。这种情况可能是因为文件在传输过程中损坏、…

蓝桥等考C++组别八级003

第一部分:选择题 1、C++ L8 (15分) 整数16,20的最大公约数(公因数)是( )。 A. 1 B. 2 C. 4 D. 80 正确答案:C

机器学习入门(第二天)——感知机

概述 每个算法都是为了解决一类问题&#xff0c;或者说解决之前的问题所创造出来的&#xff0c;而感知机&#xff0c;在解决一类问题的时候也暴露了很多问题&#xff0c;变相的推动了以后的算法的改进方向。 知识树 苹果表示相对重要的 直观介绍 现在有一盘红豆和绿豆&#…

Caused by: java.lang.NoClassDefFoundError: org/mybatis/logging/LoggerFactory

今天项目启动时报错&#xff0c;刻意记录一下&#xff0c;具体报错部分日志如下&#xff1a; Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name sqlSessionFactory defined in class path resource [com/baomidou/mybatis…

2014年8月20日 Go生态洞察:Go在OSCON的精彩亮相

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

使用Vue.js编写页面组件的简单步骤

Vue.js是一个流行的JavaScript框架&#xff0c;用于构建交互式的用户界面。通过Vue&#xff0c;你可以轻松地构建单页面应用&#xff08;SPA&#xff09;并管理页面上的组件。在本篇博文中&#xff0c;我们将介绍如何使用Vue.js编写一个简单的页面组件。 步骤 1: 引入 Vue.js …

Redis+整合SpringDataRedis

Nosql和缓存的背景 数据库架构设计的发展史 第一阶段&#xff1a;单库&#xff1a;随着访问量的增加出现了性能问题 第二阶段&#xff1a;缓存&#xff1a;通过缓存&#xff0c;缓解数据库的压力&#xff0c;优化数据结构和索引 第三阶段&#xff1a;读写分离&#xff1a;数据…

分割list 批量插入数据指定条数数据

一、代码层面切割好list&#xff0c;然后插入 // package org.apache.commons.collections4; 先将list切成1000条一份 List<List<DeptDO>> p1 ListUtils.partition(deptList, 1000); for (List<DeptDO> deptDOS : p1) { // 1000条一次批量插入systemDeptMa…