缓存前需确认计算耗时超50ms、参数可序列化且变化少、结果允许短暂过期;键应规范结构+json_sort_keys排序+业务前缀;缓存操作须在逻辑层而非模板;失效策略要主动删除、更新即删、加锁防击穿,并按粒度拆分缓存键。

缓存计算结果前,先确认是否真该缓存
ThinkPHP 的 Cache::get() 和 Cache::set() 很容易套上就用,但复杂计算是否值得缓存,得看三件事:计算耗时是否稳定超过 50ms、输入参数是否可序列化且变化不频繁、结果是否允许短暂过期(比如秒级/分钟级)。如果计算本身只花 5ms,或者每次参数都带 time() 或用户 session ID 这类强动态值,强行缓存反而增加键管理负担和命中率低下的开销。
用唯一缓存键匹配多维输入参数
复杂计算往往依赖多个变量(如日期范围、分类 ID、排序字段、分页偏移),直接拼字符串易冲突或难维护。推荐用 md5(serialize($params)) 生成键,但注意:数组顺序敏感,['a'=>1,'b'=>2] 和 ['b'=>2,'a'=>1] 序列化结果不同。更稳妥的做法是先规范参数结构:
$params = [
'date_from' => $dateFrom,
'date_to' => $dateTo,
'category' => (int)$catId,
'sort_by' => in_array($sortBy, ['sales', 'score']) ? $sortBy : 'sales',
];
$cacheKey = 'report_summary_' . md5(json_encode($params, JSON_UNESCAPED_UNICODE | JSON_SORT_KEYS));
关键点:
-
JSON_SORT_KEYS消除键序影响,比serialize()更可控 - 对整型、枚举类参数做显式类型约束(如
(int)、in_array()校验),避免字符串"1"和整数1生成不同键 - 键名加业务前缀(如
report_summary_),方便后期批量清理
绕过模板层,在逻辑层完成缓存读写
别在控制器里查完数据再丢给模板去“判断要不要显示”,也别在视图里调 Cache::get() —— ThinkPHP 视图不具备缓存上下文感知能力,且模板中缓存操作会破坏逻辑分层。正确位置是模型方法或服务类内部:
// app/service/ReportService.php
public function getSummary($dateFrom, $dateTo, $catId, $sortBy)
{
$params = compact('dateFrom', 'dateTo', 'catId', 'sortBy');
$cacheKey = 'report_summary_' . md5(json_encode($params, JSON_UNESCAPED_UNICODE | JSON_SORT_KEYS));
$result = Cache::get($cacheKey);
if (false !== $result) {
return $result;
}
// 执行真实计算(可能含 JOIN、GROUP BY、子查询等)
$result = Db::table('order')
->whereBetweenTime('create_time', $dateFrom, $dateTo)
->where('category_id', $catId)
->group('status')
->field('status, count(*) as cnt, sum(amount) as total')
->select();
// 注意:大数组建议用 array_values() 去掉关联键,减少序列化体积
Cache::set($cacheKey, $result, 3600); // 缓存 1 小时
return $result;
}
常见陷阱:
- 忘记检查
false !== $result:缓存值本身可能是null或0,用if (!$result)会导致误判 - 未设置过期时间:
Cache::set($key, $val)默认永不过期,线上容易堆积无效键 - 在事务中写缓存:若事务回滚,缓存已写入却未同步失效,造成脏数据
缓存失效策略比缓存本身更关键
ThinkPHP 没有内置的「缓存穿透」「缓存雪崩」防护,靠手动控制。三个必须动作:
- 写数据库的同时,主动删对应缓存键:
Cache::delete($cacheKey),而不是等它自然过期 - 对高频变更的数据(如订单状态更新),用「更新即删」而非「更新即写」,避免写入脏结果
- 为防止突发流量击穿缓存(大量请求同时发现缓存失效并触发计算),可在获取失败时加一层互斥锁(例如用
Cache::store('redis')->lock()),但注意 ThinkPHP 6.1+ 才原生支持锁,旧版需自己基于 Redis SETNX 实现
最常被忽略的是:缓存键粒度太粗。比如整个报表用一个键,但其实只有「销售额汇总」部分变动频繁,「用户地域分布」几乎不变。拆成 summary_sales_XXX 和 summary_geo_XXX,各自设置不同过期时间,才真正省资源。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











