ThinkPHP读取器getXXXAttr默认不缓存,需手动实现;预热应放在app/bootstrap.php中;setAttr与getAttr共用时需同步清理缓存;关联查询需单独预热。

模型里加 getXXXAttr 方法后缓存不生效?
ThinkPHP 的读取器(getXXXAttr)默认每次查模型都会执行,哪怕字段值没变、逻辑纯计算,也不会自动缓存。这不是 bug,是设计使然——框架无法判断你的读取器是否无副作用。
常见错误现象:User::find(1)->age 每次调用都重新算年龄(比如基于 birthday 字段),数据库没压力,CPU 白耗。
- 必须手动在读取器里加缓存逻辑,推荐用
think\Cache或直接绑定到模型实例属性 - 别在
getXXXAttr里做 DB 查询或远程请求,否则缓存意义全失 - 注意缓存 key 要包含影响结果的所有输入,比如
birthday和当前日期,否则跨天不更新
启动时预热「热点模型字段」该往哪写代码?
不能塞在 common.php 或配置文件里——那时 ORM 还没初始化;也不能放 AppService 的 boot(),太晚,中间件和控制器可能已触发模型读取。
正确位置是 app/bootstrap.php(或 public/index.php 中 require __DIR__.'/../app/bootstrap.php'; 后、App::run() 前)。
- 先确认缓存驱动已就绪:
Cache::has('warmup:user:1')能正常返回 - 只预热真正高频访问的 ID,比如首页 Banner 用的
Article主键列表,别全表扫 - 用
User::withoutEvent()->field(['id','nickname','avatar'])->select($hotIds)避免触发钩子和冗余字段
setAttr 和 getAttr 一起用时缓存容易错乱?
当字段同时有写入器(setXXXAttr)和读取器(getXXXAttr),且都依赖同一原始字段(如 data JSON 字段),极易出现“改了但读不到新值”的情况。
根本原因是:ThinkPHP 在设置属性时会先走 setXXXAttr 处理并存入 $this->data,但后续读取仍会再次调用 getXXXAttr,而后者若没感知到原始字段变更,就会返回旧缓存。
- 读取器缓存 key 必须包含原始字段的哈希,例如
md5($this->data['data'] ?? '') - 写入器中修改完原始字段后,主动清掉对应读取器缓存:
Cache::delete("user_age_{$this->id}") - 更稳的做法:把计算结果也存进模型的
$this->data,读取器优先返回它,仅当不存在时再算再存
Redis 缓存预热后,模型关联查询还是慢?
预热单模型字段不等于预热关联。比如预热了 User 的 level_name,但 User::with('profile')->find(1) 仍会查 profile 表——因为关联是独立 SQL,不走字段读取器。
性能瓶颈常在这里转移:你以为缓存了,其实只是绕过了一个点,没动主路径。
- 关联预热要单独做,用
User::with(['profile' => function ($q) { $q->cache(true, 3600); }])->select($hotIds) - 避免在预热脚本里用
withCount或嵌套with,深度越大越容易超时或 OOM - 上线前用
Db::listen()打印预热阶段真实发出的 SQL,确认没漏关键关联
缓存预热不是“写个 foreach 循环塞数据”就完事,关键是理清字段计算链路和关联触发时机——漏掉一层,就白热一次。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











