起初,我按照正常的offset(跳过几条)limit(查询几条)去分页,却发现了一个问题。 按照每页3条,时间倒叙分页的情况大致如下: 可以看到,每一页的后面部分总会重复。甚至数据并发量大的情况下,分页会出现数据缺失,或者一直重复的现象 其中缘由大致如下: 根据微信公众号的思路,我的参与列表分页也就有了头绪。伪代码如下:如何在数据并发大的情况下准确分页
近期,由于业务需求,我需要做一款抽奖小程序的参与列表的接口,由于数据并发量大,遇到一个分页的坑,分享一下
10 --- 第一页
9
8
9 --- 第二页
8
7
7 --- 第三页
6
5
根据我上面提到的抽奖的参与列表业务情形为例。在用户A按照时间倒叙展示3条数据浏览第一页时,为ABC三条数据,浏览期间,又有3人参与了该抽奖,用户为DEF三人,此时用户A对参与列表进行翻页。则此时第二页的数据会与第一页无异,展示为ABC。因为进行数据库查询时当前第二页就是当时的第一页。
总结:用户A在未进行翻页操作时,如果一直有源源不断的用户在参与抽奖,导致翻页的数据缺失和重复。
这时就需要一种新的排序方法了。
如:微信公众号导出关注用户openid时,每次只会展示1万条数据,并且会返回一个next_openid的参数,并且该接口也可以直接传next_openid参数,接口会从传入的next_openid开始导出关注用户的openid
此种方法的好处就是,当公众号一直有新的用户关注的情况,有next_openid,作为定位,分页就不会乱,数据也不会缺失。
//接收分页参数
$limit = $request->input('limit', 100); //每页限制多少条
$page = $request->input('page', 1); //第几页(不传递next_id的情况下需要使用)
$start = ($page - 1) * $limit; //跳过几条
$nextId = $request->input('next_id', 0); //最后一条数据的用户主键id 第一页为0
//首先需要对抽奖的新老进行判断,进行新老判断主要是为了判断使不使用redis分页
if ($isOld) { //使用数据库进行分页
//判断是否有next_id传入
if ($nextId === 0) {
//如果没有next_id传入正常查询抽奖参与表,并进行分页
$joinUserData = LotteryJoinModel::where('lottery_id', $request->input('lottery_id'))
->select('user_id') //查询用户主键id
->offset($start) //跳过几条
->limit($limit) //限制条数
->orderBy('create_time', 'desc') //创建时间倒叙
->get()
->toArray();
} else {
//有next_id的话,需要使用next_id查询出其参与时的id
//首先,获取next_id在参与表的位置(id) TODO::参与id为自增的id才能这样使用,否则需要另外的排序字段,如:创建时间
$nextIdJoinId = LotteryJoinModel::where('lottery_id', $request->input('lottery_id'))
->where('user_id', $nextId)
->value('id');
$joinUserData = LotteryJoinModel::where('lottery_id', $request->input('lottery_id'))
->where('id', '<', $nextIdJoinId) //<倒序查询的话,需要以小于next_id参与id为条件
->select('user_id')
->offset($start)
->limit($limit)
->orderBy('create_time', 'desc')
->get()
->toArray();
}
//循环处理成和redis一致的数据(将数据里面的用户id拿出来)
$joinUserIds = [];
foreach ($joinUserData as $joinUserDatum) {
$joinUserIds[] = $joinUserDatum['uid'];
}
} else { //使用redis数据进行分页
//redis查询同样需要判断是否传入next_id
if ($nextId === 0) { //正常分页
//倒序进行分页
$joinUserIds = $redis->zRevRange(
$prefix . RedisKey::LOTTERY_JOIN . $request->input('lottery_id'), //此键是存放参与用户主键id的redis键
$start,
$start + $limit - 1
);
} else { //如果有next_id
//倒序获取nextId所在的位置
$rank = $redis->zRevRank($prefix . RedisKey::LOTTERY_JOIN . $request->input('lottery_id'), $nextUserId);
//根据next_id所在位置分页查询
$joinUserIds = $redis->zRevRange(
$prefix . RedisKey::LOTTERY_JOIN . $request->input('lottery_id'),
($rank + 1),
$rank + $limit
);
}
}
//返回当前页最后一个用户主键id
$nextId = end($joinUserIds);
//循环获取用户信息
$joinUsers = [];
foreach ($joinUserIds as $joinUserId) {
$joinUsers[] = UserModel::getInfoByFields((int)$joinUserId, ['avatar', 'name', '...']);
}
$data = [
'join_users' => $joinUsers,
'next_id' => $nextUserId,
... //一些附加参数,如是否有下一页,参与总数等。根据业务情况进行返回
];
return $data;