问题描述
今天遇到了一个bug, 非常奇怪,实习生用代码了一个limit 的分页,定时任务执行时候发现每次都会丢一半的数据。
仔细查看了日志发现共有18页,但是从第10页开始每次查询到的数据都是0条 ???
哈哈哈,有点蒙
原因分析
然后仔细梳理了一下业务逻辑发现了,原来犯了如此低级的一个错误, 😭😭😭
业务逻辑:
起一个定时任务,消费100条数据,采用分页每次读取50条,消费成功后更新数据状态,然后再取下一页
因为生产环境数据量太大,所以在本机造了20条数据,每次读4条 进行测试
代码运行分析如下:
第一轮
//查询总数
SELECT COUNT(1) from students s2
//for 循环分页查询
select * from students s where s.sInt_10 = 0 order by s.sId ASC LIMIT 0, 4
<结果返回的是sID包含:1,2,3,4>
//消费完成更新数据状态
UPDATE students set sInt_10 = 1 where sId in (1,2,3,4)
第二轮
select * from students s where s.sInt_10 = 0 order by s.sId ASC LIMIT 4, 4
<结果返回的是sID包含:9,10,11,12>

//消费完成更新数据状态
UPDATE students set sInt_10 = 1 where sId in (9,10,11,12)
到这里发现问题了,居然返回的是9,10,11,12 而不是5,6,7,8; 说明原本设想的第2页数据被跳过了;
忽然反应过来了,因为我们加了where 条件导致第一次更新了1,2,3,4的状态后,第二次再查询只是查了1,2,3,4之外的数据,但是我们的limit 又改变了offset; 所以导致第二次查询丢1页(5,6,7,8);第三次查询丢2页(5,6,7,8; 13,14,15,16)
解决方案
每次都读4条,一直到读取不到数据为止
第二次查询
select * from students s where s.sInt_10 = 0 order by s.sId ASC LIMIT 0, 4

更新第二次查询到的数据状态
UPDATE students set sInt_10 = 1 where sId in (5,6,7,8)
第三次查询
select * from students s where s.sInt_10 = 0 order by s.sId ASC LIMIT 0, 4

更新第三次查询到的数据状态
UPDATE students set sInt_10 = 1 where sId in (13,14,15,16)
这样就一切正常了
总结
其实这个分页问题是一个很基础的问题,但是这次踩坑是因为实际项目中大家都很少写sql了。中型公司有ORM,大型公司有专业DBA,程序员只需要RPC 走微服务就好了;
所以基础知识还是很重要
网友评论