一、问题描述
1.起源。我在做一个在线考试系统的项目中,希望用户回答的每一道题都有相应的记录:一是记录每道题的正确、错误的次数;二是记录用户每个错题,用来形成用户的错题集。
2.实现。由于考试的试卷中有不定数量的试题,所以我使用循环在判断用户回答是否正确,并在循环中记录数据,并写入数据库。
3.瓶颈。由于每提交一个答卷,就会产生数十次或更多的数据库写入或更新的操作,因此会耗费大量的时间。
二、问题分析
1.由于每道题都要被记录两次:一次是更新试题对错的次数,二次是记录到错题集中,如果每个试卷有20道试题,那么每个答卷就要进行40次数据库操作,导致数据库压力很大。
2.由于每个试题都是不同的数据库记录,因此难以批量更新(有的是新增记录,有的是更新记录)。
3.如果大量用户并发提交,那么服务器就可能崩溃,速度缓慢。虽然我的小网站平时没有那么多人来光顾,但我自己开发的系统总不能最终成为不可用的废品吧,所以必须优化下!
三、解决思路和方案
1.因为有大量数据库操作,所以我首先考虑到的是使用redis来提升性能。
2.由于更新数据的操作既有新增记录,又有更新记录,所以必须把更新操作和操作从逻辑上分离出来。
3.我使用的是thinkphp框架开发,模型层自带批量新增的函数addAll(),因此,新增数据的操作就用它解决了;然后通过网上搜索或自己编写一个函数,拼装批量更新sql语句,形如如下的语句结构:
UPDATEcategories SET
display_order = CASE id
WHEN 1 THEN 3
WHEN 2 THEN 4
WHEN 3 THEN 5
END,
title = CASE id
WHEN 1 THEN 'New Title 1'
WHEN 2 THEN 'New Title 2'
WHEN 3 THEN 'New Title 3'
END
WHERE id IN(1,2,3)
4.通过redis的hash表来记录要更新或新增的数据,在适当的时机,触发数据库操作,通过php操作redis的数据,执行成功后就删除那些redis数据,从而解决瓶颈问题。
附:以下是对试题对错记录的优化,对于用于错题集的优化代码类似,因此只展示前者的代码了。
5.redis记录过程:
$check ? $field = 'r' : $field = 'w';//检查对错
$redis = new \Redis();
$redis->connect('127.0.0.1',6379);
$redis->hIncrBy(‘qid_check_log’,'qid.'.$qid.'.'.$field,1); //键值累加1
6.把redis数据转存到mysql中:
$redis = new \Redis();
$redis->connect('127.0.0.1',6379);
$data_cache = $redis -> hGetAll(‘qid_check_log’);
$temp = array(); //待更新的数据
$i = 0;
//要增加的数据
foreach($data_cache as $key=>$num){
$arr = explode('.',$key);
$qid = $arr[1];
$field = $arr[2];
$temp[$i]['qid'] = $qid;
$ids[] = $qid;//需要更新的试题id
$temp[$i][$field] = $num;
$redis -> hDel($qid_check_log,$key);
$i++;
}
if(empty($ids)) return true;//如果没有更新,则直接返回
//获取原来的数据
$map['qid'] = array('in',$ids);
$old_data = M('questionlog') -> where($map) -> select();
$old_data2 = array();
foreach($old_data as $one){
$old_data2[$one['qid']] = $one;
}
unset($old_data);
//合并数据
foreach($temp as &$one){
if(isset($one['r'])){
$one['r'] = $old_data2[$one['qid']]['r'] + $one['r'];
}else{
$one['r'] = $old_data2[$one['qid']]['r'];
}
if(isset($one['w'])){
$one['w'] = $old_data2[$one['qid']]['w'] + $one['w'];
}else{
$one['w'] = $old_data2[$one['qid']]['w'];
}
}
$re = batch_update('questionlog',$temp,'qid');//执行批量更新
后记:其实当初我在编写程序的时候没有设计好,如果设计得合理的化,也许不需要redis也能完成这个优化,不过,也正好因为这个机会,让我在项目中真正用到了redis,感受到了它的速度优势,哈哈!
来源:51CTO
作者:七彩极
链接:https://blog.51cto.com/qicaiji/2114175?source=drh