### 2018 年 12 月 4 日 发布 大部分`ThinkPHP`应用都可以简单通过缓存提升应用性能,本文就来总结下`ThinkPHP`的数据缓存使用和注意事项。 >[info] 这里说的缓存仅针对应用的数据缓存,不包含系统的缓存优化设置的使用(可以参考之前的[如何有效提高ThinkPHP的应用性能](https://blog.thinkphp.cn/843679)一文)。 `ThinkPHP5`的应用数据缓存主要包含三块:数据缓存、查询缓存和请求缓存。 ## 数据缓存 如果你有些场景对数据的实时性要求并不高,那么选择数据缓存是一个很好的解决办法,数据缓存指的是直接通过`Cache`类管理缓存,例如下面是一个简单的例子。 ~~~ use think\facade\Cache; if (Cache::has('user_data')) { $users = Cache::get('user_data') } else { $users = User::where('score', '>', 100)->select(); // 缓存用户数据30秒 Cache::set('user_data', $users, 30); } ~~~ 缓存的配置通过`config/cache.php`文件配置。 事实上,上面的查询代码可以简化成: ``` $users = Cache::remember('user_data', function(){ return User::where('score', '>', 100)->select(); },30); ``` >[danger] 为了避免缓存写入失败的并发问题,`remember`方法默认会自动增加缓存锁机制。 如果你希望获取一次缓存数据后就立刻删除该缓存,可以使用`pull`方法。 ``` $users = User::where('score', '>', 100)->select(); // 缓存用户数据30秒 Cache::set('user_data', $users, 30); // 获取缓存数据后删除 $users = Cache::pull('user_data'); ``` 更多的缓存操作可以参考官方手册的[缓存章节](https://www.kancloud.cn/manual/thinkphp5_1/354116)。 >[info] 要发挥缓存的优势,关键还在于缓存类型的合理选择,尤其在高并发的情况下,你需要选择分布式缓存类型。而且有时候选择数据库类型作为缓存机制并不会比文件缓存性能低,尤其是使用内存表的话。目前`Redis`作为缓存甚至和数据库同步的方案已经是较为常见,网上有很多相关的文章。 ## 查询缓存 查询缓存是专门针对数据库查询而设计的一种缓存简化策略,不需要添加复杂的代码,可以更简单的实现数据查询的数据缓存。代码改造量也相对较小,只需要给你的查询或者写操作添加一个`cache`链式方法,例如上面的数据缓存代码使用查询缓存方式可以改成: ``` $users = User::where('score', '>', 100) ->cache('user_data', 30) ->select(); ``` >[info] 查询缓存支持所有的查询操作,不仅是`find`,也包括`select`/`find`/`value`/`column`方法以及衍生方法。 一旦你的查询中使用了`cache`方法并开启了查询缓存,系统在进行实际的数据库查询之前会首先检查缓存数据是否有效,如果有效则直接返回而不再查询,如果查询缓存不存在或者无效则会在本次查询后对查询结果进行缓存。 `cache`方法其实有三个参数,最简单是用法是只传入一个`true`,表示开启查询缓存,缓存有效期则使用系统的缓存设置。 ``` // 查询缓存的有效期使用系统设置 $user = User::where('id', 10) ->cache(true) ->find(); ``` 如果你需要设置缓存有效期,可以使用 ``` // 查询缓存30秒有效 $user = User::where('id', 10) ->cache(30) ->find(); ``` 你还可以设置一个缓存失效时间 ``` // 查询缓存将在2019-1-1 失效 $expireTime = new \DateTime('2019-01-01'); $user = User::where('id', 10) ->cache($expireTime) ->find(); ``` 如果你需要在查询外部操作缓存数据,可以设置一个缓存Key ``` // 设置查询缓存的Key $user = User::where('id', 10) ->cache('user_cache', 30) ->find(); // 在查询外部操作缓存 $user = Cache::get('user_cache'); Cache::rm('user_cache'); ``` >[danger] 推荐自己设置缓存`key`的方式使用查询缓存,好处是可以减少缓存标识的生成开销提高性能,而且可以在外部操作缓存。 查询缓存目前不支持关联模型的数据自动缓存,如果你需要缓存关联模型的数据,需要使用数据缓存把整体的查询结果缓存起来,可以使用下面的代码: ``` $users = Cache::remember('users', function(){ return User::with('profile') ->where('status', 1) ->select(); },30); ``` ### 缓存自动更新 缓存数据的自动更新是缓存管理的一个难点,但对于一些规范的查询缓存,框架已经帮你实现了数据变化的时候自动更新缓存。 这里的缓存自动更新是指一旦数据更新或者删除后会自动清理缓存(下次获取的时候会自动重新缓存)。 自动更新规则一,当你在使用查询缓存后,当删除或者更新数据的时候,可以通过调用相同缓存`key`的`cache`方法,则会自动更新(清除)缓存,例如: ~~~ $users = User::cache('user_data')->select([1,3,5]); User::cache('user_data') ->where('id', 1) ->update(['name'=>'thinkphp']); $users = User::cache('user_data')->select([1,5]); ~~~ 虽然使用了相同的缓存标识`user_data`,但最后查询的数据不会受第一条查询缓存的影响,最终的查询缓存会变成第二次查询的结果。 >[danger] 这种方式的缓存自动更新适用于所有的查询方法,但必须确保查询和更新或者删除使用相同的缓存标识才能自动清除缓存。 自动更新规则二,如果使用`find`/`get`方法并且使用主键查询的情况,不需要指定缓存标识,会自动清理缓存,例如: ~~~ User::cache(true)->find(1); User::where('id', 1)->update(['name'=>'thinkphp']); User::cache(true)->find(1); ~~~ 如果是基于主键的查询缓存,发生数据变化的时候会自动更新缓存,而且更新的时候无需指定`cache`方法。 ## 请求缓存 数据缓存和查询缓存只是缓存了部分数据,控制器的执行逻辑开销依然存在,请求缓存则更进一步,可以缓存当前请求的整个响应输出的页面(仅对`GET`请求类型有效)。 可以在路由规则里面调用`cache`方法设置当前路由规则的请求缓存,例如: ~~~ // 定义GET请求路由规则 并设置3600秒的缓存 Route::get('new/:id','News/read')->cache(3600); ~~~ 第二次访问相同的路由地址的时候,会自动获取请求缓存的数据响应输出,并发送`304`状态码。 如果你没有使用路由或者只定义了部分路由的话,可以通过设置全局请求缓存,并支持设置排除规则,在`app.php`文件中增加配置定义如下: ~~~ 'request_cache' => true, 'request_cache_expire' => 3600, 'request_cache_except' => [ // 排除列表和user模块 '/blog/index', '/user', ], ~~~ 排除规则里面只需要设置不使用请求缓存的地址的开头部分(不区分大小写)。 >[info] 路由中设置的请求缓存依然有效并且优先,如果需要设置特殊的请求缓存有效期就可以直接在路由中设置。