Eager Loading 用 with() 预加载关联数据可避免 N+1 查询,而 load() 和 lazy() 仍可能导致多次查询;Blade 中慎用 $loop 魔法属性以防预读整集合;生产环境应固化配置与路由缓存;队列任务只传模型 ID 而非对象。

查数据库 N+1 问题用 Eager Loading 而不是 lazy()
很多 Laravel 应用变慢,根本不是框架本身拖累,而是循环里反复查数据库。比如遍历 100 个 User,每个都调 $user->posts,实际发了 101 条 SQL。
正确做法是在查询时就预加载关联数据:
User::with('posts.comments')->get();注意别写成 load() 或 lazy() —— 前者是运行时补查(仍可能 N+1),后者是惰性加载,表面省事,实际更容易漏掉触发点。
-
with()在主查询阶段就拼好 JOIN 或 IN 查询,一次到位 - 嵌套层级深时,用点号语法:
with('posts.tags', 'profile'),别层层->load() - 如果只查部分字段,加
select():with(['posts' => function ($q) { $q->select('id', 'title', 'user_id'); }]),避免拉回大字段(如content)拖慢网络和内存
Blade 模板里别在循环里调 $loop->index 以外的魔法属性
$loop 看着方便,但 $loop->remaining、$loop->last 这类属性会强制 Laravel 预读整个集合,对大数据集(比如上千条记录的 Collection)直接卡住页面渲染。
真正需要判断“是不是最后一个”,用 PHP 原生方式更稳:
@foreach($items as $index => $item)
{{ $item->name }}
@if($index === count($items) - 1)
<!-- 最后一项 -->
@endif
@endforeach-
$loop->index、$loop->iteration是安全的,它们不依赖集合长度 - 如果用的是
paginate(),$loop属性在分页器里基本不可靠,别信 - 模板里调
$item->relation->name且没预加载?等于悄悄触发 N+1,比写错$loop还难排查
缓存配置和路由别总用 php artisan config:clear 和 route:clear
开发时顺手清缓存没问题,但上线后频繁执行这两个命令,等于让所有请求都去重新编译配置/路由文件,CPU 尖刺、首字节延迟(TTFB)飙升都是常态。
生产环境应该用固化缓存:
- 部署后只跑一次:
php artisan config:cache和php artisan route:cache - 改了
.env?别手动改完就清缓存——config:cache会读取当前.env生成静态数组,下次改.env必须重跑 - 用了
env()在代码里动态读变量?缓存后失效。必须改成config('app.feature_flag')并在config/app.php里定义默认值
队列任务里别传 Eloquent 模型对象,改传 ID
把整个 User 对象塞进 dispatch(new SendWelcomeEmail($user)),序列化时会连带关系、访问器、原始查询语句一起打包,体积暴涨,Redis 内存吃紧,反序列化还可能失败(比如模型类被删了或命名空间变了)。
标准解法永远是传 ID,任务里再查:
class SendWelcomeEmail implements ShouldQueue
{
public function __construct(public int $userId) {}
<pre class='brush:php;toolbar:false;'>public function handle()
{
$user = User::findOrFail($this->userId);
// ...
}}
- 哪怕你加了
public $deleteWhenMissingModels = true,也挡不住序列化膨胀 - 如果要用软删除模型,记得在
handle()里显式调withTrashed()或onlyTrashed(),别依赖模型构造时的状态 - 上传文件路径、临时 URL 这类易过期数据,也别存在任务属性里,要在
handle()里实时生成
性能优化不是堆技巧,是每一步都得想清楚数据怎么来、怎么走、什么时候该扔。Laravel 的便利性背后,藏着不少“自动发生”的隐式行为,盯不住这些,缓存再猛也救不回一个 N+1 的列表页。



















