✅扫表任务,如何写SQL可以避免出现跳页的情况?
典型回答
这其实是一个非常常见的问题,只不过面试中直接这么问他的不多,但是工作中遇到的很多,有的面试官也会问一个扫表任务具体如何写SQL等问题。
一般来说,我们的扫表都是定时任务执行的,比如说我们要扫描一张表中所有INIT状态的数据,因为内存有限,所以不太可能一次性都扫描出来,只能分页,所以,最简单的方式就是:
select from table where … state ='init' order by limit 0,100
通过以上SQL实现分页查询,然后上面是查询第一页,如果接着要查第二页,则用以下SQL:
select from table where … state ='init' order by limit 100,200
看上去好像没啥问题是吧?但是实际上这个查询,会导致跳页的问题。
因为以上SQL的页面的跳转,有一个前提,那就是数据是不变的,即状态是不会发生改变的,可是,如果在查询完第一个之后,我们是需要处理这些数据的,处理完之后,如果成功了,是要把状态从INIT改成其他状态比如SUCCESS的,这时候,第一页(0,100)的数据已经变成SUCCESS,那么你再根据INIT查询(100,200)的数据的时候,虽然查询的是INIT状态的第二页,但是实际上查询的已经是第三页了,因为第一页被你改成SUCCESS了。
那么也就意味着,第二个的数据被你跳过了。并没有实际执行。如果想要让这个查询生效,那么应该第二次查询的时候也查第一页,即还是select from table where … state ='init' order by limit 0,100,这样只要能保证前面100个数据状态变成SUCCESS之后,我永远查询第一个的INIT就行了。
但是这个假设太理想了,实际执行的时候,有些任务可能就是无法执行的,那么就会导致第一页的无法成功的数据不断堆积,堆积的数据不断被执行,但是他会一直占用这个分页的窗口,导致后面的数据无法被执行。甚至严重的会导致任务陷入**死循环,**即第一个数据永远无法成功,但是又无限的循环执行。
那么到底如何解决呢?有一个好的方案,那就是选择一个游标,也就是我们标记上第一个处理过的最大的id,下一页查询的时候,用select from table where … state ='init' and id> ${last_max_id} order by id limit 0,100 这样的查询。
只要每次我们都把上一页的最大id获取到,就能保证下一次的查询数据一定是不重复且不丢的。