数据并发量大的情况下分页
编辑时间:2020-02-04 17:14:15 作者:666666

如何在数据并发大的情况下准确分页

近期,由于业务需求,我需要做一款抽奖小程序的参与列表的接口,由于数据并发量大,遇到一个分页的坑,分享一下

起初,我按照正常的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;

 点赞 3
 收藏 0
 分享
来说两句吧
最新评论
    暂无评论
天气预报
万年历
2015年
7月
返回今天

博客声明

本博客属个人所有,不涉及商业目的。遵守中华人民共和国法律法规、中华民族基本道德和基本网络道德规范,尊重有节制的言论自由和意识形态自由,反对激进、破坏、低俗、广告、投机等不负责任的言行。所有转载的文撰写页面章、图片仅用于说明性目的,被要求或认为适当时,将标注署名与来源。避免转载有明确“不予转载”声明的作品。若不愿某一作品被转用,请及时通知本人。对于无版权或自由版权作品,本博客有权进行修改和传播,一旦涉及实质性修改,本博客将对修改后的作品享有相当的版权。二次转载者请再次确认原作者所给予的权力范围。

本博客所有原创作品,包括文字、资料、图片、网页格式,转载时请标注作者与来源。非经允许,不得用于赢利目的。本博客受中国知识产权、互联网法规和知识共享条例保护和保障,任何人不得进行旨在破坏或牟取私利的行为。本博客声明以简体中文版为准,不对其他语言版本负责。

如有侵权请及时联系我进行处理。邮箱youseeim666@163.com