IOTXING

记录技术学习之路

0%

记一次数据库问题排查

案发现场

在任务开始的时候,状态为INIT,然后需要等待定时器去取状态为INIT的任务,修改状态然后执行。但是在下午开始,突然发现INIT的任务一直处于INIT的状态,而定时器却是5s轮询一次,没有头绪,只能继续等,刚好当时在做别的事情,就没有在意。

吃过饭之后,看了下错误日志,看到了下面的内容

image-20200508203023740

通过输出,能够发现错误是因为事务锁一直在等待获取锁,处于卡主的状态

再往下看,又看到了新的报错内容

连接超时

这个意思是说连接池满了,无法获取新的连接。 我使用的是公司提供的二方包来进行数据库连接,没有单独配置maxActive,因此最大连接数只有10个,满了之后就无法获取新的了。

因为没有数据库的权限,所以只能联系DBA进行问题排查。查询之后发现,刚好是有是个连接在处于等待的状态,而有一个事务一直没有释放掉,因此就导致了死锁。

所有的对状态的更新都无法进行,一直处于INIT状态,而定时器又会5s取一次处于INIT状态的任务,更改状态去执行,因为之前状态未更新,事务锁也没有释放,新的事务也是无法执行,然后就一直建立新链接,建立事务,直到达到连接池的最大限制。

通过DBA手动帮我关闭掉卡主的事务线程之后,问题瞬间得到了恢复,但是根源还是没有查到,天真的我以为是超时的问题,边在代码里面给所有的事务都加了超时时间,然后重新发布。

过了一会,又发现了还是一样的情况,状态没有改变,只是错误日志变成了下面这样

事务超时

这是因为加了超时,所以事务在达到超时时间没有提交之后,便报错退出了。

这么频繁的出现,肯定不是偶然,应该是代码的问题。

通过查询所有有事务的代码,找到了元凶。

代码

在一个事务里面,我设置了线程的sleep,而本来这个sleep不是在这个方法里面的,当时思考了一番,把代码挪了过来,没想到给自己留了一个坑。当前任务设置了执行前的等待时间300s,也就是5分钟。也就是说我们当前的事务会等待五分钟后才会提交,这导致在定时器取的时候,状态一直是INIT,然后就会继续创建事务,然后陷入了死循环的状态。

教训

  1. 不要在事务里面使用sleep等阻塞线程的方法
  2. 根据实际场景来判断是否使用事务
  3. 事务一定要设置超时时间,防止死锁
  4. 任务一定设置超时时间,如果在超过了指定的时间没有结束,直接更改任务状态,做个兜底