需求:对大量学生进行各类成绩进行排名
原理:把成绩通过redis的PIPELINE分批添加到有序集合,取出后更新到临时表,再一次性更新到大表数据避免大表锁过长时间
直接上代码:
public static function countRank($paperId)
{
$redis = Cache::store('redis')->handler();
$redisSelect = Config::get('cache.stores.redis.select');
$redis->select($redisSelect);
$key = Config::get('cache.stores.redis.prefix') . 'paper:rank:' . $paperId;
$studentScore = PaperRankModel::where(['paper_id' => $paperId])->column('id,total_score');
if (empty($studentScore)) {
return true;
}
//这里可以一次打包1k~2k条命令,视实际情况而定 我懒得写了
$pipe = $redis->pipeline();
foreach ($studentScore as $v) {
$pipe->zAdd($key, $v['total_score'], $v['id']);
}
$pipe->exec();
$data = $newRank = [];
$i = $oldGradeRank = 1;
$rank = $redis->zRevRange($key, 0, -1, true);
foreach ($rank as $k => $v) {
$newRank[] = ['pk' => $k, 'total_score' => $v];
}
foreach ($newRank as $k => $v) {
if ($k == 0 || $v['total_score'] != $newRank[$k - 1]['total_score']) {
$gradeRank = $i;
$oldGradeRank = $i;
} else {
$gradeRank = $oldGradeRank;
}
$data[] = ['paper_id' => $paperInfo['id'], 'pk' => $v['pk'], 'rank' => $gradeRank];
$i++;
}
PaperRankTmpModel::insertAll($data);
PaperRankModel::alias('rank')->join('paper_rank_tmp tmp', 'rank.id = tmp.pk')->where(['rank.paper_id' => $paperId])->update(['rank.grade_rank' => Db::raw('tmp.rank')]);
PaperRankTmpModel::where(['paper_id' => $paperId])->delete();
$redis->del($key);
$studentScore = null;
return true;
}