最近在线上项目发现下面两个SQL分页查询到的结果有可能是重复的:
第一页的SQL:
SELECT * FROM `users` WHERE show_in_cp=1 order by is_online desc,updated_at desc,id desc limit 0,10
第二页的SQL:
SELECT * FROM `users` WHERE show_in_cp=1 order by is_online desc,updated_at desc,id desc limit 10,10
其中is_online字段用来标识用户是否在线,1为在线,0为不在线;
搜了一个网上说的都是在排序中再加一个主键排序,如order by id就好了,实际测试时发现这种方法也不可行,还是会有重复的。
项目组的一个小伙提出的如下方案,采用后目前还没发现分页数据有重复的,方案如下:
1、在某个查找第一页数据时redis缓存中设置一个“第一页的时间”,这个时间在用户请求第一页时设置;
2、用户在请求非第一页数据时,查询条件中加一个updated_at < “第一页的时间”的条件,即SQL变为以下方式:
SELECT * FROM `users` WHERE show_in_cp=1 and updated_at < '2021-02-22 16:54:48' order by is_online desc,updated_at desc,id desc limit 10,10
这样即使用户在接下来的分页中再查看数据,这样也不会出现由于is_online和updated_at值的变化导致前后分页有重复的现象,完美解决。
设置“第一页的时间”样例代码如下:
$cacheKey = "api:cplist:findpeople:firstpagetime:{$uid}";
$firstPageTime = Redis::get($cacheKey);
if (is_null($firstPageTime) || $page == 1) {
Redis::setEx($cacheKey, 3600, date('Y-m-d H:i:s'));
$firstPageTime = Redis::get($cacheKey);
}
这种方案应该也没多大问题,唯一一个问题就是用户不刷新第一页数据时永远看不到最新的。
不过想一想,微博、头条类似的feed流的,他们的查询条件中应该有一个条件也是从某个时间点开始往前取数据的,最新的数据还是要等到用户再刷新第一页的数据时才能给用户展示。就像在头条客户端点“首页”键后会重新加载当前tab页的最新的数据一样,只不过人家的推荐算法更复杂而已。