Yii 1.x的管理后台分页导致的性能问题

上午客服反应管理后台在售管理和求购管理打开缓慢,我跟踪了下原因 我猜想原因如下:

  • 交易相关的,走了交易服务的接口,接口比较缓慢
  • 没加limit或者查询条件比较复杂没走索引

查看代码,发现是php代码直接查询数据库的,排除第一个原因,接下来往直接查数据的方向查,可能原因有

  1. 交易数据负载比较高,查询响应慢
  2. 管理后台使用了外网地址连接数据库
  3. 查看交易数据的近期慢SQL,是否有管理后台请求的慢SQL,发现端倪
![page1.png](2-Areas/blog/public-blog/hugo/content/posts/Yii%201%20x的管理后台分页导致的性能问题/page1.png)

本地开启浏览器显示sql调试信息

page2.png

发现会select count(*) from trade_product_sell,这个应该是为了分页,但是线上表数据已经5600万了,这个sql得执行9秒,能不慢吗 管理后台的分页插件都会执行这个count操作

public function getTotalItemCount($refresh=false)
   {
        if($this->_totalItemCount===null || $refresh)
            $this->_totalItemCount=$this->calculateTotalItemCount();
        return $this->_totalItemCount;
   }

找到问题后,怎么处理,现状如下

  1. 此处代码为yii1.x的分页逻辑,查询总条数,然后计算分页,此代码在管理后台使用的非常多
  2. 此分页逻辑在针对小表查询的时候一点问题也没有,且使用比较方便
  3. 原则是不能改框架,且一定要获取到总条数,前端页面才能正常显示分页

解决方案

  1. 既然总条数的获取不可避免,那就优化count(*)的性能

    • 根治的方法:针对大表进行归档处理,这个是交易系统的表,处理起来需要时间,可能要排到下季度了
    • 治标的方法: 赋值一个假的总条数,这个的问题是体验不好,有的表只有几行,却显示一堆分页
    • 治标的方法: 将查询总数缓存起来,虽然第一次会慢,但是之后都挺快的
  2. 代码实现

    1. 不能修改框架代码,防止之后升级框架版本的时候覆盖了

    2. 可以选择修改基础的模型类,增加一个方法,进行总数缓存,且可以按需使用,针对大表及对总数精确度不敏感的场景

    3. BasicAcitveRecord类中增加方法/** * 缓存一下分页组件使用的数据总条数 * 适用场景: * 对count不要求精确的,表比较大的,select count(*) 比较慢的那种,如果不缓存,每次都得查count(*)比较慢 * 不适用场景: * 对总数要求精确的 * 实现原理,使用表名+查询条件的md5作为key,缓存此条件下的查询总条数 * @param CActiveDataProvider $dataProvider :分页使用的dataProvider */ public function cacheTotalCount(CActiveDataProvider $dataProvider) { /** * @var $redis ARedisCache */ $redis = Yii::app()->cache; $md5 = md5(json_encode($dataProvider->getCountCriteria()) . $dataProvider->model->tableName()); $count = $redis->get(RedisKeyService::getCachedTabelCountKey($md5)); if ($count != null) { $dataProvider->setTotalItemCount($count); } else { $redis->set(RedisKeyService::getCachedTabelCountKey($md5), $dataProvider->getTotalItemCount(), 3600); } }

    4. 使用方式

      
              $model = new TradeTransfer();
              $model->setAttributes([
                  'id' => $id,
                  'receiver_steam_id' => $receiverSteamId,
                  'sender_steam_id' => $senderSteamId,
                  'status' => $status,
             ], false);
              $dataProvider = $model->search($orderAssetId);
              $dataProvider->pagination->setPageSize(10);
              //原有的代码增加下面这行
              $model->cacheTotalCount($dataProvider);
      
              $this->render('index', ['dataProvider' => $dataProvider]);
      
  3. 优化成果,接口响应时间从10秒多到1秒多

    page3.png

    page4.png