Laravel怎么优化数据库查询_Laravel如何解决N+1问题【心得】

夏墨酱_4374

夏墨酱_4374

2026-03-30

290人浏览

原创

预加载关联应使用 with() 一次性载入,避免 N+1 查询;需配合外键索引、合理使用 withCount() 统计、慎用全局作用域中的 with(),并通过 query log 和 EXPLAIN 验证优化效果。

laravel怎么优化数据库查询_laravel如何解决n+1问题【心得】

用 with() 预加载关联,别等循环里查

N+1 本质是:查 1 条主表数据,再为每条数据单独查 1 次关联表,变成 N 次额外查询。最直接的解法就是把“后面要查的”提前一次性载入。

比如用户列表页要显示每个用户的头像和所属部门,别这么写:

foreach ($users as $user) {
    echo $user->avatar->url;        // 触发第 2 次查询
    echo $user->department->name;  // 触发第 3 次查询
}

改成:

$users = User::with('avatar', 'department')->get();

这样只发 3 条 SQL:1 条查 users,1 条查 avatars(IN (...)),1 条查 departments(IN (...))。

  • with() 支持嵌套,如 with('posts.comments'),但注意深度越大,内存占用越高
  • 如果只在部分模板里需要某个关联,用 load() 延迟预加载更灵活,比如 $user->load('profile')
  • 避免 with('relation') 和后续 whereHas() 混用导致意外全表扫描——先 whereHas 过滤,再 with

慎用 eagerLoad() 和全局作用域里的 with()

有些同学会在模型的 boot() 里加 static::addGlobalScope(new MyScope),然后在 scope 里调用 with(),这会出问题。

Laravel 的全局作用域不能触发预加载,with() 在 scope 里只是被忽略,不会报错,但 N+1 照样发生,非常难排查。

  • 全局作用域只适合加 where、orderBy 这类查询修饰,不负责数据加载
  • 如果多个地方都需要固定预加载,考虑封装成作用域方法,比如 public function scopeWithProfile($query),再显式调用 ->withProfile()
  • 测试时用 DB::enableQueryLog() + dd(DB::getQueryLog()) 看实际发了几条 SQL,比猜靠谱

关联字段没索引?with() 也救不了你

预加载能减少查询次数,但每条预加载 SQL 本身可能很慢——尤其是关联字段没建索引时。

laravel-specialist
laravel-specialist

精通 Laravel 10+,遵循现代 PHP 实践的 Laravel 专家。擅长优雅语法、Eloquent ORM、队列系统及企业级特性...

下载

典型表现:预加载 posts 很慢,看日志发现 select * from posts where user_id in (1,2,3,...1000) 执行了 800ms。

  • 检查外键字段是否加了索引,比如 posts.user_id 必须有索引,否则 IN 查询会全表扫
  • MySQL 5.7+ 对长 IN 列表有优化,但超过几千个 ID 仍建议分批,Laravel 的 chunkById() 可配合使用
  • 用 EXPLAIN 看预加载语句的执行计划,重点看 type 是不是 ref 或 range,别是 ALL

复杂条件别硬塞进 with(),用 withCount() 或子查询替代

想查“每个用户有多少个已发布文章”,有人会写 with('posts' => fn ($q) => $q->where('status', 'published')),结果发现 $user->posts 变成筛选后的集合,但数量对不上预期——因为 with() 是加载数据,不是统计。

真正要的是数量,就该用:

User::withCount(['posts' => fn ($q) => $q->where('status', 'published')])->get();

这样生成的是 SELECT ..., (SELECT COUNT(*) FROM posts WHERE posts.user_id = users.id AND status = 'published') as posts_count,高效又准确。

  • withCount() 返回的是整数字段,不占内存,适合列表页做 badge 数字
  • 如果既要数量又要部分数据(比如最新 3 篇),拆成 withCount() + with(),别试图一个 with() 全包
  • 注意 withCount() 默认字段名是 {relation}_count,自定义用 as('published_posts_count')

关联查询优化不是堆 with() 就完事;它得和索引、查询意图、数据量级一起看。最容易被跳过的其实是外键索引——加完可能性能直接翻倍,但没人记得去 explain 一眼。

相关专题

更多
laravel组件介绍
laravel组件介绍

laravel 提供了丰富的组件,包括身份验证、模板引擎、缓存、命令行工具、数据库交互、对象关系映射器、事件处理、文件操作、电子邮件发送、队列管理和数据验证。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

2024.04.09

877

10

laravel中间件介绍
laravel中间件介绍

laravel 中间件分为五种类型:全局、路由、组、终止和自定。想了解更多laravel中间件的相关内容,可以阅读本专题下面的文章。

2024.04.09

855

9

laravel使用的设计模式有哪些
laravel使用的设计模式有哪些

laravel使用的设计模式有:1、单例模式;2、工厂方法模式;3、建造者模式;4、适配器模式;5、装饰器模式;6、策略模式;7、观察者模式。想了解更多laravel的相关内容,可以阅读本专题下面的文章。

2024.04.09

2508

10

thinkphp和laravel哪个简单
thinkphp和laravel哪个简单

对于初学者来说,laravel 的入门门槛较低,更易上手,原因包括:1. 更简单的安装和配置;2. 丰富的文档和社区支持;3. 简洁易懂的语法和 api;4. 平缓的学习曲线。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2024.04.10

4701

7

laravel入门教程
laravel入门教程

本专题整合了laravel入门教程,想了解更多详细内容,请阅读专题下面的文章。

2025.08.05

6870

22

laravel实战教程
laravel实战教程

本专题整合了laravel实战教程,阅读专题下面的文章了解更多详细内容。

2025.08.05

4516

13

laravel面试题
laravel面试题

本专题整合了laravel面试题相关内容,阅读专题下面的文章了解更多详细内容。

2025.08.05

8489

7

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

2026.03.04

1516

29

Laravel 框架安装指南
Laravel 框架安装指南

本指南详解 Laravel 框架安装全流程,涵盖 PHP 8.1+ 环境配置、Composer 依赖管理工具安装及国内镜像源优化。重点演示使用 composer create-project 命令创建 Laravel 10/11 项目,解决常见安装错误与依赖冲突。从环境搭建到项目初始化,助您快速完成 Laravel 开发环境部署,为后续 Web 应用开发奠定基础。适合 PHP 初学者与框架迁移开发者参考。

2026.04.09

215

6

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
如何安装 Composer
如何安装 Composer

共1课时 | 221人学习

Composer手册
Composer手册

共0课时 | 0人学习