最近在优化代码的时候,突然想起来TP5的数据库操作中有个cache,之前也用过,印象里就是在缓存时间内,请求的速度会大大加快,但是修改数据会导致不能及时更新。当初还比较年轻,没有深入去搞清楚,只是不再使用cache了而已,现在刚好有机会,就来稍微学一学吧。
很可惜,不论是官方文档还是网上搜索出来的结果,基本上都只是告诉我们如何去使用它,完全没有说到它的工作原理之类的,无奈,只能去慢慢读源码了。
首先让我感到疑惑的是,这个cache和我们平常用的缓存Cache有什么区别?
如果单单从功能上看的话,好像两者没有任何区别,都能设置key值和有效期,以及打标签也都支持。随后我做了个实验,手动设置key名,然后用cache()助手函数去读取,结果如我所料,读取出来的结果果然是select的结果。
$result = db('my_table')->where($where)->cache('key',10)->select();
var_dump(cache('key')); //结果和$result一样
不过实验归实验,还是得去源码里看看具体是如何实现的。
进入/thinkphp/library/think/db/Query.php中,找到cache方法,可以看到,这里只是设置了属性,真正的使用还不在这里,还得去select、find、value、column里面看。
//源码太长了,就不一一复制了public function cache($key = true, $expire = null, $tag = null){ // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) { $expire = $key; $key = true; } if (false !== $key) {$this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag];} return $this;}public function select($data = null){ ...... if (empty($options['fetch_sql']) && !empty($options['cache'])) { // 判断查询缓存 $cache = $options['cache']; unset($options['cache']);$key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); $resultSet = Cache::get($key);} ...... if (isset($cache) && false !== $resultSet) {// 缓存数据集
$this->cacheData($key, $resultSet, $cache);} ......}protected function cacheData($key, $data, $config = []){ if (isset($config['tag'])) { Cache::tag($config['tag'])->set($key, $data, $config['expire']); } else {Cache::set($key, $data, $config['expire']);}}
结论:可以很明显地看出,不论是写入缓存还是从缓存中读取,都是与我们常用的Cache一样的,唯一不同的是,如果你不指定key名的话,他会根据操作的数据库名、表名以及主键ID等信息,帮你生成一个32位密文,你也不用担心万一key名重复导致缓存覆盖了。
疑惑二:当数据更新时,缓存会怎么样呢?
按照文档上的说法,要么手动在update等更新操作中添加cache,来实现手动更新缓存;要么使用find方法并且使用主键查询,就会自动清理缓存。手动指定缓存倒没什么问题,除了不触及缓存操作的新增之外,在数据更新后缓存都会被清除,然后在查询时重新被写入。PS:增删改查中,新增操作是不触及缓存的,这也是缓存要谨慎使用的原因,虽然能够极大地增加效率,但是不能反映数据的及时更新。
然后就是什么情况下更新数据能够自动清除缓存的问题了。比较麻烦的是,涉及到缓存操作的话,是否使用主键作为查询条件还不一样。也就是说,会有以下2*2+2*2共八种组合。
用主键做条件进行查询+用主键做条件进行修改——清除
用主键做条件进行查询+用主键做条件进行删除——清除
用主键做条件进行查询+不用主键做条件进行修改——不清除
用主键做条件进行查询+不用主键做条件进行删除——不清除
不用主键做条件进行查询+用主键做条件进行修改——不清除
不用主键做条件进行查询+用主键做条件进行删除——不清除
不用主键做条件进行查询+不用主键做条件进行修改——不清除
不用主键做条件进行查询+不用主键做条件进行删除——不清除
虽然还有很多情况没有测试到,比如更新操作的数据是否为缓存的数据、查询和更新操作的条件是不是一样等等,即使测试的结果和文档上描述的一样,但是还是感觉说服力不够强,还得再去源码里找找原因。这里以update操作为例。
//如果有设置缓存名,则直接从cache中读取
if (isset($options['cache']) && is_string($options['cache']['key'])) {
$key = $options['cache']['key'];
}
......
//如果没有手动设置缓存,则只能依靠主键ID以及操作的表来识别,否则没有办法识别出来
} elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) {
$key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind);
}
结论:只有当查询和修改操作都使用主键ID作为条件时,才能实现自动清除缓存。
所以说,数据库缓存并不是随便用的,如果使用不当,很容易影响数据的时效性和用户的体验。如果真的有必要使用的话,最好还是不要偷懒用自动清除缓存,还是手动设置缓存名字,以及在更新操作时指定清除哪个缓存。
好了,不知不觉又花了半个晚上,今天就这样吧,洗洗睡了。
另外,如果你有兴趣,或者是有问题想要与我探讨,欢迎来访问我的博客:https:mu-mu.cn/blog