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

排查生产环境: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.lryc.cn/news/239243.html

相关文章:

  • 140.【鸿蒙OS开发-01】
  • npm install 下载不下来依赖解决方案
  • 接口自动化中cookies的处理技术
  • PHP 安装
  • 小程序常见操作
  • STM32F4串口USART发送为00的解决方案
  • 重磅解读 | 阿里云 云网络领域关键技术创新
  • 【蓝桥杯省赛真题45】Scratch九宫格游戏 蓝桥杯scratch图形化编程 中小学生蓝桥杯省赛真题讲解
  • 物联网AI MicroPython学习之语法 ADC数模模块
  • 详解Python中哈希表的使用。站在开发者角度,与大家一起探究哈希的世界。
  • 详解python淘宝秒杀抢购脚本程序实现
  • 使用ChatGPT创建Makefile构建系统:使用Make运行Docker
  • 算法设计与分析复习--分支界限法
  • Https攻击怎么防御
  • 网络知识学习(笔记二)
  • 万字解析设计模式之组合模式、亨元模式
  • HTTP之常见问答
  • java伪共享问题
  • 【Ubuntu】Ubuntu arm64 部署 Blazor Server 应用
  • Android加固为何重要?很多人不学
  • 【C/PTA】函数专项练习(一)
  • SUDS: Scalable Urban Dynamic Scenes
  • 蓝桥杯算法双周赛心得——迷宫逃脱(记忆化搜索)
  • nodejs+vue线上生活超市购物商城系统w2c42
  • 飞翔的小鸟
  • 浅析OKR的敏捷性
  • Linux+qt:创建动态库so,以及如何使用(详细步骤)
  • 如何将Docker的构建时间减少40%
  • 二分查找——经典题目合集
  • 在Jupyter Lab中使用多个环境,及魔法命令简介