Yii2性能优化之:数据库查询缓存完美解决方案

我们在做项目的时候,为了最大化提升项目的执行速度,缓存必不可少,各种开源框架也提供了丰富多彩的的缓存实现方案。比如,HTML整页缓存、片段缓存、程序层面的数据库查询缓存、数据库层面的查询缓存等等。每一种方式都有其适合的应用场景。比如,对于访问量比较大的网站首页,比较适合用缓存整页的方式;比如,某个页面的一个统计表格,不需要每次都进行查询,使用的时候直接从缓存里面取,片段缓存就非常合适;比如,对于一些频繁请求数据库的sql查询,数据库中的值有时候没有改变,却被频繁进行查询明显是不合理的,尤其是对一些比较耗时的查询,如果将这部分查询的数据放入缓存,则会明显缩短程序的响应时间,这种场景下就需要使用数据库查询缓存。与数据库层面的查询缓存相比,程序层面的缓存对于程序员来讲是友好且更容易控制的。本文主要讲述应用程序层面的数据库查询缓存。

实际上,不管对于何种缓存使用方案,都面临两个问题:

  1. 缓存命中率问题。
  2. 缓存过期问题。
    第一个问题不是本文讨论的重点,我们着重解决第二个问题。

缓存一般都需要设置一个过期时间,被缓存的数据过期之后,需要更新一下,设置缓存的示例代码如下:

    /**
     * @param $key 缓存key
     * @param $value 缓存的值
     * @param null $duration 缓存的过期时间
     */
    public function set($key, $value, $duration = null);

注意:对于数据库查询缓存这样的场景来讲,上面这种常规设置缓存的方式有两个问题:

  1. 在缓存有效期内,数据库的数据被更新了,但通过读缓存获取的还是旧数据怎么办?
  2. 数据库数据并没有更新,但缓存过期了,必然需要重新进行一次数据库查询并存入缓存,从某种角度讲,这一次查询也是没有必要的。

一个假设:
是否存在一种设置缓存的方式,可以实时监测数据库表是否更新过,以此来决定是否读取缓存?
答案是肯定的。我们可以通过引入缓存依赖的概念来解决此问题。
缓存依赖是除了缓存时间之外的另一个让缓存失效的条件,它和缓存时间共同决定着缓存是否有效,如果其中一个条件失效,那么缓存会被重新设置。那么对于使用【数据库查询缓存】的场景我们只需要做两件事,一个是获取数据最后被更新的时间,另外一个是Yii2是怎样设置数据库缓存依赖的。

获取数据表最后一次缓存时间,以msyql为例

以mysql数据库user表为例:

##获取user表的最后一次更新时间。
SELECT
    *
FROM
    information_schema.TABLES 
WHERE
    TABLE_SCHEMA = DATABASE ( ) 
    AND information_schema.TABLES.TABLE_NAME = 'user'\G

##或者使用
show table status like 'user';

我们执行第一条sql,获取结果如下:

*************************** 1. row ***************************
  TABLE_CATALOG: def
   TABLE_SCHEMA: zhyc_prod
     TABLE_NAME: user
     TABLE_TYPE: BASE TABLE
         ENGINE: InnoDB
        VERSION: 10
     ROW_FORMAT: Compact
     TABLE_ROWS: 1271
 AVG_ROW_LENGTH: 1250
    DATA_LENGTH: 1589248
MAX_DATA_LENGTH: 0
   INDEX_LENGTH: 0
      DATA_FREE: 4194304
 AUTO_INCREMENT: 1308
    CREATE_TIME: 2019-01-02 15:28:02
    UPDATE_TIME: 2019-01-05 17:44:23
     CHECK_TIME: NULL
TABLE_COLLATION: utf8_general_ci
       CHECKSUM: NULL
 CREATE_OPTIONS: 
  TABLE_COMMENT: 账号表

返回的结果中有两个关键字段
CREATE_TIME:表的创建时间
UPDATE_TIME:表的最后一次更新时间

注意:Innodb引擎在mysql5.7.2版本才开始支持UPDATE_TIME这个字段,在低于这个版本的mysql中,该字段值是空的。

InnoDB: Beginning with MySQL 5.7.2, UPDATE_TIME displays a timestamp value for the last UPDATE, INSERT, or DELETEperformed on InnoDB tables. Previously, UPDATE_TIME displayed a NULL value for InnoDB tables. For MVCC, the timestamp value reflects the COMMIT time, which is considered the last update time. Timestamps are not persisted when the server is restarted or when the table is evicted from the InnoDB data dictionary cache. -----引用自Mysql官网

Yii2框架的缓存依赖实现

yii2框架实现了几种缓存依赖机制。

  1. 文件依赖
  2. 表达式依赖
  3. 标签依赖
  4. 数据库依赖
    对于Yii2的缓存依赖,网上有大量文章。此处不做赘述。这里我们只用到数据库依赖。
    废话不多说,直接上代码:
getSchema()->getRawTableName(static::tableName()));
        $sql = "select CREATE_TIME,UPDATE_TIME 
                from information_schema.TABLES 
                where TABLE_SCHEMA=database() 
                and information_schema.TABLES.TABLE_NAME = '" . $table . "'";

        return parent::find()
            ->cache(static::$queryDuration, new DbDependency([
                'sql' => $sql,
                'reusable' => true //对于一个会话,只执行一次查询即可
            ]));
    }

}

注意:本类可以作为项目的自定义的超类,其他的所有model类需要继承它之后才可以用缓存。

通过对find函数的重写,我们达到了所有子类都可以使用缓存的效果,下面来测试一下:

  1. 修改yii框架的log配置,将sql单独写到一个文件里去
'components' => [
      ...
        'log' => [
            'traceLevel' => 0,
            'targets' => [
                [
                    'class' => 'yii\log\FileTarget',
                    'levels' => ['profile'],
                    'logVars' => [],
                    'categories' => ['yii\db\Command*'],
                    'logFile' => '@runtime/logs/sql.log',
                ],
            ],
        ],
      ...
    ],

  1. 运行你的yii2项目,然后再看下sql.log。

我们啰嗦了这么多,实际的代码量并不大。在本人的测试中,发现有两种查询总是不能被缓存。一个是ActiveQuery::viaTable() 函数,另一个是ActiveQuery::via()函数,至于为什么不能,留给小伙伴自己去实践吧。。。

你可能感兴趣的:(Yii2性能优化之:数据库查询缓存完美解决方案)