Mysql Innodb数据库锁等待超时故障排查实践

1、现象
Mysql Innodb数据库锁等待超时故障排查实践_第1张图片
关键信息:Lock wait timeout exceeded;try restarting transaction
出现该错误说名数据库发生了锁请求超时,直接联想到发送了死锁。
此时,另外一个信息,告诉你请求锁等待超时的SQL,进而定位到dao层的方法。

2、排查思路与过程
1)查看数据库锁,了解数据库锁占用情况

从上面表中、发现两个线程在同时申请同一行数据的锁,但令人疑惑的是,无论是多个线程请求同一把锁,这个不构成死锁的条件,那为什么会出现无法获取锁的情况呢?为了进一步确认数据锁的情况,然后尝试用 show engine innodb status;查看一下Innodb当前的状态信息:
Mysql Innodb数据库锁等待超时故障排查实践_第2张图片
从这里也能看出,数据库层面目前有两个锁请求(等待),还是构成不了死锁。
这个时候,我们从数据库层面已经获取到足够的信息,就是这条语句正在尝试获取锁,但锁已经被占用。
该SQL 使用了 select ... for update语句,该锁在什么时候会释放呢?事务提交或事务回滚时自动释放?难不成是在业务层事务提交或回滚有问题,但平时使用正常呀,故暂时应该可以排查业务层的事务控制部分的bug,接下来我们应该重点,从SQL语句去跟踪代码,梳理整个业务流程,业务层为什么没有结束。

2)业务层代码排查
排查思路:
首先根据sql,找到dao层的方法,然后看dao层方法的调用链。
然后根据代码跟踪,发现该方法只在一个方法中被调用:
该方法(amethod1)的伪代码为:
获取ZK锁(uid) -----> 获取数据库锁(select ... for update) ------>释放zk锁 ---->方法结束 ,(数据库锁等事务内方法全部允许完毕,在事务结束时释放锁)。

在继续跟踪amethod1方法的调用链(因为要最终要反向找到业务入口的方法)之前,我们初步分析一下这个过程。
线程t1、t2同时进入该方法,
其中一个线程,比如t1,获得zk锁, 获取数据库锁, -------》释放zk锁,(释放zk锁同时,t2,线程将获取zk锁)----》继续在事务内执行,,,事务执行成功后,释放锁。
此时t2,阻塞, t1释放 尝试获取数据库锁,由于t1,事务此时未结束,故进行数据库锁层面的等待, 获取数据库锁,,然后是释放ZK锁,然后提交事务,结束。
按照上面的流程,按道理来说是可以按部就班的执行完成的,但为什么在数据库层面会出现两个锁。从上面的过程分析,如果t1,在释放ZK锁,只要提交或回滚,都会释放锁,如果由于某种原因,事务一直不结束,会出现什么问题呢?就会出现目前的现状,一个事务持有select for update锁(1行数据,但迟迟不释放锁),另外一个事务在等待该锁。
分析到这里,原因基本分析出来来,就是线程t1在释放ZK锁后,另外一个事务t2占有ZK锁,但线程t1,却不释放数据库锁,是什么样的原因导致t1始终无法执行,它又在等待等待哪把锁。按照经验,难不成t1,在后续的业务处理过程中,又需要申请t2占有的zk锁,此时我们可以尝试看应用服务器的线程栈,但我认为目前并没有必要,有了合理的猜测后,我们应该立马继续回溯amethod1的代码调用,找到业务的入口。
继续跟踪,
Mysql Innodb数据库锁等待超时故障排查实践_第3张图片
amethod1的入口,t1线程执行完amethod1(更新用户余额)的方法后,继续回到这个方法,继续执行下面的代码,继续跟踪后发现在baseBlacnePointBiz.plusBalance方法,在更新用户积分时,竟然也用的是更新积分的ZK锁,导致死锁。问题解决了,,只需要将更新用户积分的锁换成一把新的锁,该业务就能正确执行。问题解决之。

你可能感兴趣的:(Mysql Innodb数据库锁等待超时故障排查实践)