本文聚焦 Web 常见的父子/多对多关系,通过 `with`/`loadMissing` 与聚合加载消除 N+1 并配合缓存与索引获得稳态优化。所有指标可用 Telescope/Query Log 验证。
## 适用版本与前提
- PHP 8.2/8.3,Laravel 10.x。
- 已安装 `laravel/telescope`(可选)或开启 Query Log。
## 可复现的 N+1 基线
模型关系示例:`Post hasMany Comment`。
// routes/web.php
Route::get('/posts', function () {
\DB::enableQueryLog();
$posts = \App\Models\Post::latest()->take(50)->get();
foreach ($posts as $p) {
// 触发 N+1(每条 Post 额外 1 次查询)
$p->comments->count();
}
return [
'queries' => count(\DB::getQueryLog())
];
});
验证:无预加载时 1(posts)+ N(comments)条查询,观察 `queries` 值与耗时。
## 解决方案 1:预加载关系(with)
Route::get('/posts-fast', function () {
\DB::enableQueryLog();
$posts = \App\Models\Post::with(['comments' => function ($q) {
$q->select('id','post_id'); // 限字段,减负载
}])->latest()->take(50)->get();
foreach ($posts as $p) {
$p->comments->count();
}
return [
'queries' => count(\DB::getQueryLog())
];
});
验证:查询数量降为固定 2 条(posts+comments),耗时显著下降;在 Telescope 中对比两路由的 `p95`。
## 解决方案 2:缺失时再加载(loadMissing)
适用:服务层有条件访问关系,避免重复加载。
$posts->loadMissing('comments:id,post_id');
## 解决方案 3:聚合加载(withCount/withSum)
避免为统计再次遍历关系:
$posts = \App\Models\Post::withCount('comments')
->withSum('comments as comments_words_sum', 'words')
->latest()->take(50)->get();
验证:只追加 1 次聚合查询,遍历时不触发额外 SQL。
## 解决方案 4:缓存与失效策略
$key = 'posts:index:v1';
$posts = cache()->remember($key, 60, function () {
return \App\Models\Post::with('comments:id,post_id')
->latest()->take(50)->get();
});
// 失效(在评论创建/删除后):
Event::listen(\App\Events\CommentChanged::class, fn()=> cache()->forget('posts:index:v1'));
验证:命中缓存后数据库查询降为 0;通过 `redis-cli monitor` 或 Query Log 观察差异。
## 数据库层协同:索引与选择字段
- 为外键 `comments.post_id` 建立索引:`CREATE INDEX idx_comments_post ON comments(post_id);`
- 预加载时限制选择字段,减少网络与解析成本。
## 指标采集与对比
- Query 数:基线 ≈ 1+N → 预加载后 ≈ 2。
- 耗时:在 50 条父记录的场景,预加载常见提升 2–5 倍(视网络与序列化)。
- Telescope:对比两路由的 `p95/p99` 与内存占用。
## 注意事项
- 嵌套预加载需控制层级与字段,避免爆炸加载。
- 缓存键需带版本与租户维度;失效与幂等逻辑要在业务层保证。
- 批量加载与分页结合时,注意 `with` 与 `simplePaginate` 的协同。
## 相关文章(同分类热门)
- [Laravel 10 性能与缓存优化:Opcache、Redis、Route 缓存与队列可验证实战指南](./Laravel 10 性能与缓存优化:Opcache、Redis、Route 缓存与队列可验证实战指南.md)
- [Laravel 10 队列与并发任务处理:Redis 队列、Supervisor 与 Horizon 可验证实战](./Laravel 10 队列与并发任务处理:Redis 队列、Supervisor 与 Horizon 可验证实战.md)
## 结语
通过预加载、聚合加载与缓存协同,Eloquent 能在真实业务下稳定消除 N+1 并降低数据库压力;上述步骤可直接在你的代码与监控中验证落地效果。

发表评论 取消回复