CakePHP在isEmpty判断条件下查询缓存不生效问题
问题原因
核心问题出在CakePHP ORM的惰性执行机制和方法调用顺序错误:
- 调用
$categoriesTable->find()时不会立刻执行SQL查询,仅返回查询构造器对象,只有当你调用isEmpty()、all()、first()或者遍历查询对象时,才会真正发起数据库请求。 - 查询对象的
cache()配置,只有在查询执行前挂载才会生效:查询执行时会检测是否配置了缓存规则,有则在拿到结果后写入缓存;如果查询已经执行完成,后续再调用cache()不会触发任何缓存写入操作。
你当前的代码逻辑顺序完全颠倒:先调用$categories->isEmpty()触发查询执行(此时还没配置缓存,执行完不会写Redis),之后才调用->cache()挂载配置,此时查询已经跑完,缓存配置根本不生效,所以你紧接着读缓存永远是null,每次请求都会进入缓存未命中的分支,重复输出hello。
你观察到的另外两个现象也完全符合这个逻辑:
- 注释掉
isEmpty()判断后,查询对象不会提前执行,等到后续视图渲染触发查询时,你已经完成了缓存配置挂载,所以查询执行时会正常写入缓存,功能正常。 - 把判断改成
$categoriesTable->find('all')->isEmpty()时,isEmpty()是在一个全新的独立查询对象上调用的,你原来赋值给$categories的查询对象没有被提前触发,等到后续使用时才执行,此时已经挂载了缓存配置,所以能正常写缓存。但这种写法会额外多执行一条完全相同的SQL,属于无意义的性能损耗。
另外原有逻辑还存在一个隐患:缓存未命中时传给视图的是查询对象,缓存命中时传给视图的是缓存里存储的结果集,两种数据类型不一致,很容易触发序列化或渲染错误。
修正代码
调整逻辑顺序:先给查询挂载缓存配置,再触发查询执行,最后判断结果是否为空,如果为空就主动删除缓存,保证只有非空结果才会被缓存,同时统一返回给视图的数据类型:
public function getCategories() { $categoriesTable = TableRegistry::getTableLocator()->get('Categories'); $cachedCategories = \Cake\Cache\Cache::read('categories', 'redis_cache'); if ($cachedCategories === null) { // 先构造查询,提前挂载缓存配置 $categoriesQuery = $categoriesTable->find(); $categoriesQuery->cache('categories', 'redis_cache'); // 触发查询执行,拿到实际结果集 $categories = $categoriesQuery->all(); // 结果为空时删除缓存,避免空值被存入Redis if ($categories->isEmpty()) { \Cake\Cache\Cache::delete('categories', 'redis_cache'); } $this->set('categories', $categories); } else { $this->set('categories', $cachedCategories); } $this->viewBuilder()->setOption('serialize', ['categories']); }
内容的提问来源于stack exchange,提问作者Abu Sayed
相关产品推荐
相关产品推荐

