對一個26萬資料MYSQL表的Yii2程式優化實戰之一 【去糟粕、加快取】

阿北哥ya發表於2017-06-21

這是一篇真實案例,並不是理論課,阿北將同步我的整個優化之路,優化之路慢慢長,對大家拋磚引玉已達目的,若你也有一些優化思路,請跟貼。

當前系統情況

專案是年前一個朋友做的,客戶也是我的一個朋友,所以現在來幫忙優化,系統很簡單,就是一個選單頁面,客戶下單,然後印表機出小票,整個系統使用yii2基礎版 + MySQL5.6.29驅動。

客戶店裡每天大約走1.5-2w的流水,現在最大的表有26w資料,我切圖大家先看下。

當前資料表情況
當前資料表情況

從圖例看可能要優化的地方

  • 表引擎使用了MyISAM問題
  • order、order_box資料表瓶頸問題

我使用的工具

  • 本地環境MAMP
  • yii2-debug 神器小強
  • yii2的各種快取

優化原則

程式碼的修改最小化,儘量不動核心程式碼以防止引入bug,最後在進行資料庫和伺服器的優化。

那我們就開始吧~

客戶說後臺登陸慢

當我第一次聽客戶這樣說的時候,就已經知道慢的絕對不是登陸,這個系統沒有許可權、沒有很多日誌、沒有登陸後的事件、僅僅就是一個登陸而已。

那很可能就是慢在登陸後進入的第一個頁面而給客戶的感覺是登陸慢。

那就看看這個頁面

登陸後的頁面
登陸後的頁面

頁面邏輯很簡單,就是一個銷售圖表,每天的銷售額曲線,那麼問題最可能出現在這個圖表上,畢竟銷售額的計算看程式碼都是從訂單實時分析出來的。

用yii2-debug看一看

資料庫39次呀~~~
資料庫39次呀~~~

果不其然,yii2-debug告訴我此action整個響應時間為2.652秒,而資料庫就用了2.518秒,查詢次數39次...

看看每個查詢,每一天執行一個SQL語句,每個都用了90毫秒左右,畢竟是26w資料中拿資料。

似乎問題明朗了,解決這個資料庫查詢就解決了這個頁面,先看看這個圖表的程式碼實現

$y = date('Y',time()); //年
$m = date('m',time()); //月
$days_num = date('t',time()); //當月天數

$days = '';
$moneys = '';
for($i=1; $i<=$days_num; $i++){
    $days .= $i.',';
    $begin_time = strtotime($y.'-'.$m.'-'.$i) + Yii::$app->params['business_hour']['begin_time'];
    $end_time = strtotime($y.'-'.$m.'-'.$i) + Yii::$app->params['business_hour']['end_time'];
    $money = Order::find()
        ->where(['>','dish_id',0])
        ->andWhere(['pay_state'=>'pay'])
        ->andWhere(['>=','pay_time',$begin_time])
        ->andWhere(['<=','pay_time',$end_time])
        ->andWhere(['store_id'=>Yii::$app->admin->identity->store_id])
        ->sum('money');
    $moneys .= ($money > 0 ? $money : 0).',';
}複製程式碼

優化的步驟很簡單:首先看能不能減少查詢次數,如果不能就加速查詢,如果還不能就快取結果。

在這段程式碼中,無論今日是幾號,都進行了整月天數的查詢,我先來去掉不該進行的查詢。

修改及其簡單,只是增加了3行程式碼

for($i=1; $i<=$days_num; $i++){
    $days .= $i.',';
    if($i > date('d',time())){
        $moneys .= "0,";
        continue;
    }
    ........
}複製程式碼

但是通過減少不必要的查詢的結果是

9次查詢被幹掉了
9次查詢被幹掉了

資料庫檢索從39減少到30次,耗時從2.518秒減少到1.982秒。

對於加速查詢無外乎表型別選擇及索引的新增,本著表是所有action的表,蒼老師是世界的蒼老師,我先不動,只是記錄下dish_id、pay_state、pay_time、store_id四個欄位,後面對order表進行改造的時候再考慮他們。

然後對於這種統計類資料,沒有必要每次都實時讀取,我應該加一個快取。

加個檔案型別快取

如果你不會玩Yii2快取,請移步到 Yii2快取系列 先學習。

看這個圖表和當下程式碼,我的快取可以按照月份來,但是還要保證當天銷量的實時性,所以我快取的是本月今天之前的資料。

首先去配置檔案 config/web.php 設定

// conf/web.php
...
'cache' => [
    'class' => 'yii\caching\FileCache',
],
...複製程式碼

採用預設的就好,程式碼進行一點小手術(將今天之前的程式碼單獨處理),核心邏輯不變。

$cache = Yii::$app->cache;
$cacheKey = "month-report-{$m}";

$days = '';
$moneys = '';

//  今天之前的資料,也是我們要快取的資料
$monthDatBeforeToday = $cache->get($cacheKey);
if ($monthDatBeforeToday === false) {
    for($i = 1;$i < date('d',time());$i++){
        $days .= $i.',';
        $begin_time = strtotime($y.'-'.$m.'-'.$i) + Yii::$app->params['business_hour']['begin_time'];
        $end_time = strtotime($y.'-'.$m.'-'.$i) + Yii::$app->params['business_hour']['end_time'];
        $money = Order::find()
            ->where(['>','dish_id',0])
            ->andWhere(['pay_state'=>'pay'])
            ->andWhere(['>=','pay_time',$begin_time])
            ->andWhere(['<=','pay_time',$end_time])
            ->andWhere(['store_id'=>Yii::$app->admin->identity->store_id])
            ->sum('money');
        $moneys .= ($money > 0 ? $money : 0).',';
    }
    $monthDatBeforeToday = ['days'=>$days,'moneys'=>$moneys];
    $cache->set($cacheKey,$monthDatBeforeToday,7200);
}

$days = $monthDatBeforeToday['days'];
$moneys = $monthDatBeforeToday['moneys'];

for($i=date('d',time());$i<=$days_num; $i++){
    $days .= $i.',';
    if($i > date('d',time())){
        $moneys .= "0,";
        continue;
    }

    $begin_time = strtotime($y.'-'.$m.'-'.$i) + Yii::$app->params['business_hour']['begin_time'];
    $end_time = strtotime($y.'-'.$m.'-'.$i) + Yii::$app->params['business_hour']['end_time'];
    $money = Order::find()
        ->where(['>','dish_id',0])
        ->andWhere(['pay_state'=>'pay'])
        ->andWhere(['>=','pay_time',$begin_time])
        ->andWhere(['<=','pay_time',$end_time])
        ->andWhere(['store_id'=>Yii::$app->admin->identity->store_id])
        ->sum('money');
    $moneys .= ($money > 0 ? $money : 0).',';
}複製程式碼

當然,從編寫上講,這段程式碼可以繼續優化,但是在原理上已經完成了,我將今天之前的資料都快取下來,今天的實時讀取,這樣就滿足了這個和原來一樣的結果,為防止其他地方對訂單的修改,換成我2個小時更新一次。

用yii2-debug看看結果。

天呀
天呀

你沒看錯

  • 資料庫從39次到10次,耗時從2.518秒到0.1秒
  • Action執行從2.652秒減少到0.212秒

初步使用我們Yii2優化三原則中的兩條,Action執行提高了12倍、資料庫耗時減少了23倍。

優化的步驟很簡單:首先看能不能減少查詢次數,如果不能就加速查詢,如果還不能就快取結果。

記住它,一點點來,萬物均可優化。

還有很多

這僅僅是對統計資料採用快取小試牛刀,如果對於訂單列表這種檢索那就要用到第二條原則了(如何讓查詢語句更快)。

下一篇將為你講解 對一個26萬資料的MYSQL表Yii2程式優化實戰(真例項子)之二 【開刀資料表】

#更新記錄
第一篇 對一個26萬資料MYSQL表的Yii2程式優化實戰之一 【去糟粕、加快取】
juejin.im/post/594a7a…

第二篇 對一個26萬資料MYSQL表的Yii2程式優化實戰之二 【開刀資料表】juejin.im/post/594cf5…

(完)

本文原創釋出於微信公眾號 北哥小報 , 嚴謹的原創技術文,還有一些其他研究。

微信掃碼可以關注
微信掃碼可以關注

相關文章