对一个26万数据MYSQL表的Yii2程序优化实战之一 【去糟粕、加缓存】

2,272 阅读5分钟

这是一篇真实案例,并不是理论课,阿北将同步我的整个优化之路,优化之路慢慢长,对大家抛砖引玉已达目的,若你也有一些优化思路,请跟贴。

当前系统情况

项目是年前一个朋友做的,客户也是我的一个朋友,所以现在来帮忙优化,系统很简单,就是一个菜单页面,客户下单,然后打印机出小票,整个系统使用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.cn/post/684490…

第二篇 对一个26万数据MYSQL表的Yii2程序优化实战之二 【开刀数据表】juejin.cn/post/684490…

(完)

本文原创发布于微信公众号 北哥小报 , 严谨的原创技术文,还有一些其他研究。

微信扫码可以关注
微信扫码可以关注