with()必须在get()/paginate()前调用才生效,否则静默失效;深层关系需显式声明中间表外键;事务内禁用with()和懒加载;访问器中禁止查库,否则引发N×M查询。

直接用 with() 预加载就能解决绝大多数 N+1 问题,但 Laravel 5.5 的 Eloquent 实现有明确限制和行为边界——不按规则写,预加载就等于没写。
with() 必须放在 get() / paginate() 前,否则完全无效
这是 Laravel 5.5 最常踩的坑:预加载必须在查询执行前声明,一旦模型集合生成,with() 就再无机会介入。
-
Post::with('user')->get()✅ 生成 2 条 SQL(posts + users IN (...)) -
Post::all()->with('user')❌all()返回 Collection,with()是 Query Builder 方法,此处静默失效 -
Post::paginate(15)->with('user')❌paginate()已触发查询,with()不起作用 -
$posts = Post::get(); $posts->load('user')✅ 这是合法的补加载,但只适用于已查出的集合
嵌套关系要显式写全,且中间表字段不能漏
Laravel 5.5 不支持自动推导深层关系,点号语法(如 lists.podcasts)必须严格对应模型中定义的关系方法名,且所有外键字段必须显式包含在 select() 中。
-
Group::with('lists.podcasts')->get()✅ 前提是Group有lists()方法返回belongsToMany(List::class),List有podcasts()方法 -
Group::with(['lists' => function($q) { $q->select('id', 'name'); }])->get()❌ 漏掉group_id和list_id(中间表外键),导致映射失败,$group->lists全为 empty collection - 正确写法:
->select('id', 'name', 'group_id', 'list_id')(中间表字段必须保留)
事务里禁用 with() 和懒加载,否则锁等待或静默性能崩
在 DB::transaction() 块内调用 with() 或访问未预加载关系(如 $user->profile),会触发隔离级别冲突、锁争用,甚至不报错但响应变慢数倍。
- 错误做法:
DB::transaction(function () { $users = User::with('profile')->get(); ... });—— 事务内预加载可能被阻塞 - 正确做法:预加载提前做,事务内只操作数据
$users = User::with('profile')->get(); DB::transaction(function () use ($users) { ... }); - 更安全判断:
if ($user->relationLoaded('profile')) { ... },避免事务中意外触发懒加载
访问器里禁止查库,否则单页 100 行可能触发上千次查询
Laravel 5.5 的访问器(getXXXAttribute())是纯计算入口,一旦在里面写 Score::whereIn() 或 $this->scores(未预加载时),循环渲染立刻放大成 N×M 查询。
- 典型反模式:
public function getCalculationAttribute() { return Score::whereIn('id', $this->scores->pluck('id'))->get(); } - 修复路径:控制器中强制预加载
ExampleModel::with('scores')->get(),访问器改用内存数据$this->scores->pluck(...) - 替代方案:用
withCount()或DB::raw()聚合,把计算下推到 SQL 层,避免 PHP 层循环查库
真正卡住人的不是语法不会写,而是预加载后仍出现 N+1——往往藏在访问器、模板里的隐式调用,或事务块中那行看似无害的 $model->relation。


















