可采用四种方法缓存复杂查询结果:一、用cache::remember封装完整查询逻辑;二、用query builder的cache()方法;三、用模型观察器自动清除关联缓存;四、将结果预计算并持久化至缓存表。

如果您在 Laravel 应用中执行包含多表联结、子查询、聚合函数或动态条件的复杂数据库查询,且该查询耗时较高、结果变动频率低,则可借助缓存机制避免重复执行。以下是针对复杂查询结果实施缓存的多种方法:
一、使用 Cache::remember 封装完整查询逻辑
该方法将整个查询构造与执行过程包裹在闭包中,由缓存系统统一管理命中与未命中流程,适用于控制器或服务类中明确可控的复杂查询场景。
1、构造具备业务语义的唯一缓存键,例如"complex:orders_summary:{$start}:{$end}:{$status}",确保键名能反映所有影响结果的变量。
2、调用Cache::remember($key, $ttl, function () use ($start, $end, $status) { ... }),在闭包内构建并执行 Eloquent 或 Query Builder 查询。
3、在闭包中使用DB::table()或Order::with(['user', 'items.product'])等完整链式调用,支持join、selectRaw、groupBy等复杂操作。
4、闭包必须返回可序列化的数据结构(如集合、数组),不可直接返回响应对象或视图实例。
二、对 Query Builder 链式查询启用原生 cache() 方法
自 Laravel 8.76 起,Query Builder 提供了内置的cache()和cacheFor()方法,可在不侵入业务逻辑的前提下为任意链式查询添加缓存层,特别适合在模型作用域或 Repository 中复用。
1、在查询链末尾调用->cache(3600),指定以秒为单位的 TTL,例如DB::table('orders')->join('users', 'orders.user_id', '=', 'users.id')->selectRaw('users.name, COUNT(*) as cnt')->groupBy('users.name')->cache(3600)->get()。
2、若需自定义缓存键,可传入第二个参数:->cache(3600, 'summary:monthly:active_users'),该键将被用于存储与检索。
3、当查询中含动态绑定参数(如whereRaw('created_at > ?', [$date]))时,框架会自动将参数哈希纳入键生成逻辑,确保语义一致性。
4、注意:该方法仅对get()、first()、value()等终端方法生效,toSql()或explain()不触发缓存。
三、基于模型观察器自动清除关联缓存
当复杂查询结果依赖于多个模型的数据状态(如订单汇总依赖 orders、order_items、products 表),任一底层模型更新都可能导致缓存失效;通过监听模型事件可实现精准、及时的缓存清理。
1、为Order、OrderItem、Product模型分别注册updated和deleted事件监听器。
2、在监听器中调用Cache::forget()清除所有与该业务实体相关的复杂查询缓存键,例如Cache::forget('complex:orders_summary:*')(需驱动支持通配符,Redis 可配合keys命令实现)。
3、更优方式是使用缓存标签(Tags),在写入缓存时标记Cache::tags(['order', 'report'])->put($key, $data, 3600)。
4、在模型观察器中触发Cache::tags(['order'])->flush(),一次性清除所有带order标签的缓存项,避免手动维护键名列表。
四、将复杂查询结果预计算并持久化至缓存表
对于计算开销极大、实时性要求不高的报表类查询(如月度销售 Top 10、区域热力分布),可将其结果定期写入专用缓存表,并通过数据库查询而非内存缓存读取,兼顾一致性与性能。
1、创建迁移文件生成cache_reports表,字段包括type(VARCHAR)、payload(JSON)、expires_at(DATETIME)、created_at。
2、编写 Artisan 命令php artisan report:refresh monthly-sales,在命令中执行原始复杂查询,将结果存入cache_reports对应记录。
3、在控制器中改用CacheReport::where('type', 'monthly-sales')->where('expires_at', '>', now())->firstOrFail()获取数据,跳过 ORM 层与缓存驱动调度。
4、设置定时任务(如 Laravel Scheduler)每日凌晨执行该命令,确保数据新鲜度可控,同时避免请求高峰期触发计算。











