Laravel 模型关联与预加载:用 with/load 解决 N+1 与关联写入

test12026-09-274 次阅读

N+1 问题

当你查出 20 篇文章,再在循环里访问每篇的作者,ORM 会为每篇文章各发一条 SQL 查用户,总共 1+20 条查询。这种 N+1 在列表页尤其致命。Laravel 用预加载把关联一次性取出,把 N+1 压成 2 条查询。

预加载 with

with 在查询时通过 LEFT JOIN 或额外一条 IN 查询,把关联模型一并加载,之后访问 $post->author 不再触发数据库。

<?php

use App\Models\Post;

// 一次性取出文章与作者、评论
$posts = Post::with(['author', 'comments'])
    ->where('status', 'published')
    ->latest()
    ->paginate(20);

foreach ($posts as $post) {
    // 不额外查库
    echo $post->author->name;
}

延迟预加载 load

当模型集合已经取出,再想补加载关联,用 load 做延迟预加载,避免重新查询主表。

$posts = Post::all();
// 事后补充加载评论
$posts->load('comments');

// 单个模型也能用 load
$post = Post::find(1);
$post->load('author', 'comments');

关联写入与同步

关联不只用来读,还能直接写入。hasMany 的 create 自动填充外键;多对多用 attach/sync 维护中间表,sync 会替换整个集合,diff 的部分自动增删。

// 给文章追加一条评论,自动写入 post_id
$post->comments()->create([
    'content' => '写得很清楚',
    'user_id' => auth()->id(),
]);

// 给用户设置角色(多对多),sync 会移除未列出的旧角色
$user->roles()->sync([1, 3, 5]);

// 仅新增不删除,用 attach
$user->roles()->attach(2);

带约束的预加载

有时只想要“审核通过的评论”或“最近三篇”,可以在 with 里传闭包加约束,关联数据在 SQL 层就被过滤。

$posts = Post::with(['comments' => function ($query) {
    $query->where('approved', true)->latest()->limit(3);
}])->get();

// 只统计数量而不取全量
$posts = Post::withCount('comments')->get();
foreach ($posts as $post) {
    echo $post->comments_count;
}
  • 列表页一律用 with 预加载关联,消除 N+1。
  • 已拿到集合再补关联,用 load 而不是重新查主表。
  • 多对多维护用 sync 整体替换,attach 仅增量新增。
T

test1

文章作者

N+1 问题 当你查出 20 篇文章,再在循环里访问每篇的作者,ORM 会为每篇文章各发一条 SQL 查用户,总共 1...

分类
技术
发布时间
2026-09-27
字数
约 1429 字
阅读
4 次