實戰優化php和mysql:記錄一次實際項目中優化數據庫訪問

實戰優化php和mysql:記錄一次實際項目中優化數據庫訪問

本期中某個項目中下載報名數據模塊中,項目經理說正式環境下出現下載過慢的情況,在實際的數據量,一個文件需要半分鐘甚至一分鐘的時間生成,從而導致超時,甚至下載失敗的情況.因爲這個模塊是之前已經離職的同事寫的,因此我去接手還是有點茫然的.最後在我的優化下將下載時間優化到10s以內.還是要有點小收穫.因此寫下這一篇文章.


同步環境

首先是把線上的數據庫同步下來,這樣才能模擬出來,否則在測試環境下不過是數據鏈較小的測試數據,不還查看.同步之後發現數據量並不大,單張表,最大的不過8M,五萬行.就算是下載報名的模塊涉及到的表也不過五六張,加起來十來m的數據,按理說不應該出現訪問過慢的情況.

降低代碼粒度

先看代碼吧,好傢伙一個目錄下十幾個文件夾.只能先去下載在看到底是那個文件.打開文件後發現幾個下載模塊都混在一起,單個文件下大約在1000行左右,而且不是函數,而是一個switch case區分下載模塊.如此一來優化難度就大幅度提高了.

在這裏必須強調一個原則,那就是代碼的小顆粒度原則.

  • 同一層級下大小不宜過大.同一個層級下如果內容過多會導致閱讀障礙,也不利於優化.比如同一個目錄下十幾個文件的話就要劃分子文件夾.
  • 不要吝惜使用小函數.函數是ide友好的.尤其是添加過註釋的函數.大的模塊,劃分爲幾個小的函數是有利於調試與閱讀的.雖然有可能帶來性能的損耗,但是更有利於編寫與優化.
  • 如果某個模塊,你覺得有必要添加註釋,那麼請添加函數.

根據上面的原則,我首先把下載模塊獨立出來.有把幾個下載模塊獨立成同一個目錄下的幾個文件.這樣單一文件下就不超過200行.也就是一個屏幕可以看完的大小.

在優化的過程中通常有一個大的原則.在不熟悉代碼的前提下,儘量不要修改代碼的邏輯.在這裏我只是對於代碼進行了拆分,並沒有修改代碼.在測試環境測試一下,還是很慢,2分鐘左右.

環境優化

和線上環境比測試環境環境要快一點.ok,環境不一致這是肯定的,去線上環境看了一下,發現原來正式環境是很早配置的.並沒有運維優化過,於是在晚上,我把數據庫配置文件進行了修改,擴大了內存,線程數,php環境有按照自己之前寫好的文件配置了一下.測試一下,快了大概30s.關於如何優化數據庫配置文件,我這裏推薦高性能mysql
這本書的裏面有一個章節講這個內容.

聚焦特定模塊

深入理解計算機系統有一個推論,那就是絕大多數情況下,代碼的時間都花在某個循環模塊中.在這個結論下我們只要找到這個模塊,優化他就能大幅度的降低時間複雜度.

如何找到這個模塊,phper我有幾個建議

  • 理解整個流程,通過數據量比對,大體能夠猜測出代碼中那個模塊耗時多
  • 黑盒測試配合代碼註釋.通過註釋掉某個模塊,在測試可以找到那個模塊耗時大
  • 探針.php和操作系統都提供了 DTrace 探針,來追蹤函數的調用次數,消耗時間,系統情況.但是有一個大前提,你的代碼大量使用函數,而不是大顆粒度下堆在一起.

當然這裏我並不會使用探針(其實是我用的少).

通過黑盒測試的使用我把主要耗時的代碼模塊找到了

$gamename = $db->get('game', 'name', array("id"=> $_GET['gid']));

$row = $db->select('game_team', '*', $where);
foreach($row as $k => $v){
  ...
  ...
  $teamappl = $db->select('game_team_apply', '*', array('AND' => array("game_team_id"=>$v['id'],"type"=>"1")));  //賽事報名  
  foreach($teamappl as $k1 => $vb){
    ....獲取學生信息
    //獲取學生報名組別
    //獲取項目id 已經參加的項目 就爆出來哪個項目
    //爲關聯數組 $k 從1開始
    $item_short_name = '';//表內容中 項目的項目名稱

    foreach($item_id_array as $k3 => $v3){
        $ifitemname = $db->get('game_item_eroll', '*', array('AND' => array('gameitem_id' => $v3,'apply_id' => $vb['id'])));
        if(!empty($ifitemname)){
            $item_short_name = $db->get('gameitem', 'short_name', array('id' => $v3));
            $tmp['item'.$k3]= $item_short_name;
        }else{
            $tmp['item'.$k3] = '';
        }
        $game_type = $db->get('gameitem', 'type',array('AND' => array('id' => $v3)));//獲取賽事信息
        if(!empty($game_type) && $game_type == 2){
                $duiwu_groupname = $db->get('game_item_eroll', 'groupname', array('AND' => array('gameitem_id' => $v3,'apply_id' => $vb['id'])));//獲取賽事報名記錄
                $tmp['group'.$k3]= !empty($duiwu_groupname)?$duiwu_groupname:'';
        }

    }
  }

解釋一下上面的代碼,這是一個獲取學生報名信息的下載模塊,首先向循環報名的賽事,然後對每個賽事底下報名記錄進行循環.在每一個記錄下再循環獲取學生的報名信息,再對查找每一個學生報名過的賽事.

這樣看下來總算找到原因了.原來是在多重循環裏面多次訪問數據庫如果有很多學生的話,就會同等數量級的數據庫訪問.即便mysql是輕量級的數據庫,如此龐大的訪問依舊扛不住.在這裏我給出一個普遍的數據庫訪問經驗.

  • 避免在循環內訪問數據庫.如果必須的話,請限制循環的次數

添加索引

首先想到的辦法是優化索引.看了一下數據表,有索引但是沒有優化過(其實這一步應該放到最後一步的).關於索引的建立與使用,我之前寫過一篇文章可以進行參考.通過對幾個表添加索引,將訪問時間提高到了30s左右.

減少數據庫訪問次數

通過查看代碼我們發現在最底層的循環內,兩次訪問了同一張表.ok,這四次訪問可以進行合併

foreach($item_id_array as $k3 => $v3){

    $erollInfo = $db->get('game_item_eroll', ['groupname', 'id'], array('AND' => array('gameitem_id' => $v3,'apply_id' => $vb['id'])));
    $ifitemname = $erollInfo['id'];
    $gameItemInfo = $db->get('gameitem', ['type', 'short_name'],array('AND' => array('id' => $v3)));
    if(!empty($ifitemname)){

        $tmp['item'.$k3]= $gameItemInfo['short_name'];
    }else{
        $tmp['item'.$k3] = '';
    }
    $game_type = $gameItemInfo['type'];
    if(!empty($game_type) && $game_type == 2){

           $duiwu_groupname = $erollInfo['groupname'];
            $tmp['group'.$k3]= !empty($duiwu_groupname)?$duiwu_groupname:'';
    }
}

我們再看這一行

$gameItemInfo = $db->get('gameitem', ['type', 'short_name'],array('AND' => array('id' => $v3)));

獲取每一個賽事的信息.在這裏我們可以把他放到循環的外部,

$gameItemInfo = $db->get('gameitem', ['type', 'short_name'],array('AND' => array('id' => $item_id_array)));

然後對他進行重新排序,在循環內,只訪問數組.同理對於另個訪問也可以做一樣的操作,這樣就把內部循環的減少數據庫訪問次數.把數據庫的放到了數據庫服務器上,也便於優化數據庫.

通過上面一系列的操作,就把降低了數據庫的訪問.在重新測試一下,訪問速度提高到了10s以內.ok很不錯了,再把其他模塊優化一下,吧不必要得到數據庫訪問和循環提出來.很好已經到了可以接受的範圍內了

緩存表

通過上面的一系列優化,我們已經把代碼優化到了我們容忍的範圍內.如果還要繼續優化,卻有一個限制,那就是最後一個循環並不是縱向循環,而是對於橫向的學生進行訪問.這樣就不能把他合併到前面的循環內.如何繼續優化.這時候我的領導提出了一個辦法,添加一個緩存表,定時吧學生的報名數據彙總到一個緩存表中,與學生關聯起來,這樣在下載的時候就不需要重溫遍歷數據庫,而只是需要訪問緩存表

這樣的辦法,在高性能mysql中也有提及,那就是緩存表(彙總表)與計數器表,通過提高接口的時間來加快數據統計的效率.當然這裏我並沒有進行這一步操作.最要是是沒有時間(笑),如果之後這個項目數據量進一步擴大,那麼確實有添加彙總表的需求了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章