数据库扫表任务如何避免出现死循环
假如我们有一张表case_event,其中有一个字段state,它有三个值,分布是INIT、SUCCESS、以及 FAILED。
那么在定时任务中,我们需要把 INIT 的数据扫描出来进行执行,一般来说是这么写的 SQL:
1 | SELECT * FROM case_event |
这个 SQL 看上去没啥问题,其实就是每次扫描200条记录处理。
但是这个SQL其实是一个典型的 bad case,因为他会出现一个致命的问题,那就是可能会导致扫描任务一直无法执行。
因为上述的 SQL 相当于默认了每一条记录执行之后,都能把状态推进到 SUCCESS 或者 FAILED。但是事实上并不一定的,尤其是在一些有很复杂的业务逻辑,或者一些外部调用的时候,这个地方就变成了一个分布式事务,我们没办法保证最后的 INIT->SUCCESS 或者 INIT->FAILED 一定能成功。即还是INIT
那如果不能成功,就会导致一部分失败的状态一直处于 INIT 状态,那么他就会每次都会被扫描起来(因为他还在前200条之内),然后还是不成功,下次还会被扫描出来。
这样一方面会大大降低任务的效率,一直在重复执行这些不断失败的任务,另一方面,一旦失败的条数达到了200条,那么就意味着每次扫出来的数据都是这200条,导致后面的任务永远无法被执行到。
这里的失败指的是没法推进状态 以及 执行失败而如果你的SQL 是这么写的,那么这个问题就更大了:
1 | SELECT * FROM case_event |
相当于你在不断的重复执行那些固定的任务,而后面的很多任务一直无法被执行。
一直在重复执行者两百条永远执行失败的任务,而后面的任务就管了一点了
如何解决这个问题呢,有一个方式,那就是增加一个游标,让你的每次查询都往后移动,如:
1 | select * from task where status = 'init' and id > #{id} |
这里每次查询的时候,都把上一次的查询结果中的最大id 带过来,然后就可以避免再次扫描到重复的任务了,就可以让本次任务调度正常完成执行。