1、問題描述
1.起源。我在作一個在線考試系統的項目中,但願用戶回答的每一道題都有相應的記錄:一是記錄每道題的正確、錯誤的次數;二是記錄用戶每一個錯題,用來造成用戶的錯題集。
2.實現。因爲考試的試卷中有不定數量的試題,因此我使用循環在判斷用戶回答是否正確,並在循環中記錄數據,並寫入數據庫。
3.瓶頸。因爲每提交一個答卷,就會產生數十次或更多的數據庫寫入或更新的操做,所以會耗費大量的時間。
2、問題分析php
1.因爲每道題都要被記錄兩次:一次是更新試題對錯的次數,二次是記錄到錯題集中,若是每一個試卷有20道試題,那麼每一個答卷就要進行40次數據庫操做,致使數據庫壓力很大。
2.因爲每一個試題都是不一樣的數據庫記錄,所以難以批量更新(有的是新增記錄,有的是更新記錄)。
3.若是大量用戶併發提交,那麼服務器就可能崩潰,速度緩慢。雖然個人小網站平時沒有那麼多人來光顧,但我本身開發的系統總不能最終成爲不可用的廢品吧,因此必須優化下!
3、解決思路和方案mysql
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)redis
4.經過redis的hash表來記錄要更新或新增的數據,在適當的時機,觸發數據庫操做,經過php操做redis的數據,執行成功後就刪除那些redis數據,從而解決瓶頸問題。sql
附:如下是對試題對錯記錄的優化,對於用於錯題集的優化代碼相似,所以只展現前者的代碼了。
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); //鍵值累加1thinkphp
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,感覺到了它的速度優點,哈哈!