一天一坑系列手动提交事务,开启后未关闭导致的bug!啊,多么痛的领悟...MySQLTransactionRollbackException: Lock wait timeout exceeded

Posted 善良勤劳勇敢而又聪明的老杨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一天一坑系列手动提交事务,开启后未关闭导致的bug!啊,多么痛的领悟...MySQLTransactionRollbackException: Lock wait timeout exceeded相关的知识,希望对你有一定的参考价值。

热门系列:


1、前言

前几天,春节过完开工第一天,结果运营就反应生产环境有BUG了。真是“开门红”啊~~~

先和运营了解到线上的问题症状,然后就连接生产服务器,开始逐步排查了!


2、正文

2.1 生产环境的问题症状

症状一:查询列表的数据,有的数据会时有时无!(如ABCD四条数据,刷新页面,B数据时而出现,时而没有)

症状二:新建订单,创建成功后,数据并没有入库

前面两点是通过web端页面操作,所反映出来的异常情况!后续我们通过查看日志,还发现了另外两种日志异常!!!

症状三:程序接口会报数据库事务锁定异常,如下:

### Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction
; Lock wait timeout exceeded; try restarting transaction; nested exception is com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

症状四:数据库无法连接异常,如下:

Caused by: java.sql.SQLException: connection disabled

 

2.2 分析

①对于症状一,查询数据时有时无的情况,分析:

  • 用了缓存,缓存失效导致
  • 列表结果不一致的,是因为请求到了两个不同的服务上(即分别请求到不同的两个进程了)

②症状二分析:

  • 提交请求,发送到了其他服务上
  • 数据库配置问题,导致数据插入到了别的数据库

③症状三分析:

  • 数据库锁表,导致事务阻塞,被锁
  • 数据库事务的传递性没有正确配置
  • 事务未正确开启和关闭导致事务阻塞

④症状四分析:

  • 数据库配置出了问题,导致数据库不可连接
  • 服务器与数据库之间的连接有问题

 

2.3 解决

本篇主要是针对症状三,进行分析解决!所以其他的问题,此处暂且不做解决方案叙述了~(目前问题都已经解决了,如有兴趣的朋友,可以下方留言探讨)!

上面已经对问题进行了几个方面的分析,所以按分析的方向,开始逐一排查!

①首先,查询数据库,查看是否有被锁表的情况:

-- 查看正在被锁定的的表

show OPEN TABLES where In_use > 0;

in_use:多少个线程在使用

name_locked:是否被锁

-- 查询哪些线程正在运行

show processlist;

-- 最关键的就是state列

-- 查看正在锁的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

-- 查看等待锁的事务

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

通过上面的查询之后,发现数据表并没有被锁死的情况。所以锁表导致的事务异常,可以排除!

②排查程序中数据库事务传递性的配置,但是我们使用的是默认配置,即:

//支持当前事务,如果当前没有事务,就新建一个事务。
PROPAGATION_REQUIRED

所以,事务传递性导致的可能性,也基本可以排除了!

③事务是否没有被正确的开启和关闭呢?答案是肯定的!!

后来,从代码层面排查事务的使用情况,结果发下如下代码:

try
    TransactionStatus status = super.openTransaction("createStockEntrustOrder",     TransactionDefinition.PROPAGATION_REQUIRED);
    boolean validResult = stockEntrustList.stream().anyMatch(s-> !this.checkOrderParam(s));
    if(validResult) return new CommonResultVO(ResultConstant.PARAM_ERROR);

    ......
    ......
    transactionManager.commit(status);
catch (Exception e) 
	......
	transactionManager.rollback(status);
	return new CommonResultVO(ResultConstant.FAIL);

问题来了:此处有一个校验返回的处理,就在开启事务之后!那么此时就可能出现,校验没通过,接口中的事务开启了,但是因为直接return,导致事务没有被正确关闭掉!!!如果类似情况,出现多次,就很有可能造成事务的阻塞,锁定异常的情况的发生!

解决方式:将此处的代码进行调整,在return的地方进行事务的回滚操作!!!或是,使用注解事务

@Transactional(rollbackFor = Exception.class)

最终,此症状得以修复!!!

 


3、总结

通过此次问题的暴露,个人总结两点原因:

  • 写代码的时候,不够仔细,代码逻辑处理不够严谨
  • 代码编写完成或是测试前,没有进行自我Review,查缺补漏

所以,记录此篇文章,引以为戒,往后Coding时,需更加的谨慎,并要有良好的代码review习惯,减少bug的出现!!!

如果此篇文章有帮助到你,或是有不足的地方,欢迎留言探讨,共勉~~~~

以上是关于一天一坑系列手动提交事务,开启后未关闭导致的bug!啊,多么痛的领悟...MySQLTransactionRollbackException: Lock wait timeout exceeded的主要内容,如果未能解决你的问题,请参考以下文章

记录一次bug解决过程:resultType和手动开启事务

Kafka踩坑系列之二无限循环消费数据

二十二mysqli事务处理

分析动态代理给Spring事务埋下的坑

opensession 默认提交事务吗

一天一坑系列Springboot中AOP拦截不到指定路径的方法,拦截器失效