Yii2数据库更新时,如何正确构建与更新DataProvider缓存键值对?
这个问题我之前做Yii项目时也踩过坑!核心原因是你当前的逻辑只有在缓存键不存在时才会去查库生成新的ActiveDataProvider,但当数据库里的News数据新增、修改或删除时,缓存依然存在,所以不会触发重新查询。下面给你几个实用的解决思路,结合你的代码来调整:
1. 先给缓存加过期时间(兜底方案)
最简单的方式是给缓存设置一个合理的过期时间,这样即使没有主动清理缓存,到时间后也会自动失效并重新查询数据库。修改你代码里的set方法,加上过期参数(单位是秒):
// 比如设置1小时过期,3600秒 $mem->set("categoriesProvider", $categoriesProvider, 3600);
这个方法适合对实时性要求不是特别高的场景,优点是改动小,缺点是在过期前依然会显示旧数据。
2. 数据变更时主动清理缓存(精准方案)
要做到数据库更新后立即更新缓存,最可靠的方式是在News数据发生变更(新增、修改、删除)时,主动删除对应的缓存键。你可以在News模型里利用Yii的事件机制来实现:
在News.php模型文件中添加以下代码:
public function afterSave($insert, $changedAttributes) { parent::afterSave($insert, $changedAttributes); // 数据保存后清理缓存 $mem = new \Memcached(); $mem->addServer("localhost", 11211); $mem->delete('categoriesProvider'); } public function afterDelete() { parent::afterDelete(); // 数据删除后清理缓存 $mem = new \Memcached(); $mem->addServer("localhost", 11211); $mem->delete('categoriesProvider'); }
这样每次新增、修改或删除News数据时,都会把缓存里的categoriesProvider键删掉,下次请求页面时就会重新查询数据库并生成新的缓存。
3. 优化缓存存储方式(更稳定的方案)
其实不建议直接把ActiveDataProvider实例存入缓存,因为它包含了很多Yii框架内部的对象,序列化和反序列化可能会有潜在问题,而且如果分页参数变化,缓存的Provider也无法适配不同的分页页面。更合理的方式是缓存查询到的数据和总条数,再重新构建DataProvider:
修改你的控制器代码如下:
$mem = new \Memcached(); $mem->addServer("localhost", 11211); // 考虑分页参数,生成唯一的缓存键 $page = Yii::$app->getRequest()->getQueryParam('page', 1); $dataCacheKey = "news_list_page_{$page}"; $countCacheKey = "news_list_total_count"; // 获取缓存的列表数据 $cachedModels = $mem->get($dataCacheKey); // 获取缓存的总条数 $cachedTotalCount = $mem->get($countCacheKey); if (!$cachedModels || !$cachedTotalCount) { $whereString = "active = 1"; // 查询当前页的数据 $models = News::find()->where($whereString) ->offset(($page - 1) * 6) ->limit(6) ->all(); // 查询总条数 $totalCount = News::find()->where($whereString)->count(); // 缓存数据,设置1小时过期 $mem->set($dataCacheKey, $models, 3600); $mem->set($countCacheKey, $totalCount, 3600); $cachedModels = $models; $cachedTotalCount = $totalCount; } // 重新构建ActiveDataProvider $categoriesProvider = new ActiveDataProvider([ 'query' => News::find()->where("active = 1"), 'pagination' => [ 'pageSize' => 6, 'route' => Yii::$app->getRequest()->getQueryParam('first_step'), 'page' => $page, ], ]); // 赋值缓存的模型数据和总条数 $categoriesProvider->setModels($cachedModels); $categoriesProvider->setTotalCount($cachedTotalCount);
对应的,在News模型的事件里,要同时删除这两个缓存键:
public function afterSave($insert, $changedAttributes) { parent::afterSave($insert, $changedAttributes); $mem = new \Memcached(); $mem->addServer("localhost", 11211); $mem->delete('news_list_total_count'); // Memcached不支持模糊删除,所以可以给页面缓存设置较短的过期时间,或者在更新时遍历删除(不推荐) // 这里我们主要删除总条数缓存,页面缓存随过期时间自动更新即可 } public function afterDelete() { parent::afterDelete(); $mem = new \Memcached(); $mem->addServer("localhost", 11211); $mem->delete('news_list_total_count'); }
这种方式的优点是缓存的是纯数据,更稳定,而且分页页面各自有独立的缓存,不会互相影响。
总结
- 如果对实时性要求不高,优先用过期时间的方案,改动最小;
- 如果需要实时更新,一定要在数据变更事件里主动清理缓存;
- 长期来看,建议优化缓存内容,避免直接存储复杂的框架对象,减少潜在问题。
内容的提问来源于stack exchange,提问作者Jeni Vasileva

