Laravel 中遍历 100 条记录时若未预加载关联数据,会产生 101 次 SQL 查询,而使用 with() 预加载后可压缩至 2 次查询,性能提升显著。
原因分析
N+1 查询问题的本质是:查询 1 条主表数据后,再为每条数据单独查询 1 次关联表,变成 N 次额外查询。例如遍历 100 个 User,每个都调用$user->posts,实际会发送 101 条 SQL(1 次查 users 表,100 次查 posts 表)。这是因为 Eloquent 默认采用懒加载机制,只有在访问关联属性时才触发查询。自 Laravel 8.37 版本起,官方提供了 Model::preventLazyLoading() 配置项,可在开发环境强制暴露此类问题,任何未预加载的关联访问将抛出 LazyLoadingViolationException 异常。
解决方案:使用 with() 预加载关联数据
最直接的解法是在查询时预加载关联数据,将原本 1+N 次查询压缩为最多 2 次。正确写法:User::with('avatar','department')->get(); 这样只发 3 条 SQL(1 条查 users,1 条查 avatars,1 条查 departments)。注意 with() 必须写在 get() 之前,写成 Post::get()->with('user') 没有效果。嵌套层级深时使用点号语法:with('posts.comments.author'),会发 4 条 SQL(users、posts、comments、users 再查一次 author)。若只查部分字段,加 select() 约束:with(['posts' => function ($q) { $q->select('id', 'title', 'user_id'); }]),避免拉回大字段拖慢网络和内存。
解决方案:使用 withCount() 替代循环 count()
在模板或逻辑中对每个模型实例调用 $model->relation->count() 会触发 N 次 COUNT 查询。withCount() 将计数作为附加属性注入主查询的 SELECT 子句,仅需一次聚合查询即可完成全部统计。用法:User::withCount('posts')->get(),结果集中每个模型将新增 posts_count 属性。支持条件计数:withCount(['posts as active_posts_count' => function ($q) { $q->where('status', 'published'); }])。可在 orderBy 中直接使用该计数字段:orderBy('posts_count', 'desc')。
解决方案:事务内优化策略
在数据库事务内执行 Eloquent 查询且未合理处理关联关系时,极易触发 N+1 问题。优化方法包括:一、事务前用 with() 预加载并缓存,将关联查询从 DB::transaction() 内部移出;二、事务内用 join 替代 with(),使用 DB::table() 构建主表与关联表的 inner join 或 left join 查询;三、事务内显式调用 load() 并限制加载范围,先批量查出主模型集合,再调用$orders->load(['customer' => function ($q) { $q->select('id', 'name', 'email'); }])。
注意事项
第一,关联字段没索引时 with() 也救不了。典型表现:预加载 posts 很慢,看日志发现 select * from posts where user_id in (1,2,3,1000) 执行了 800ms。检查外键字段是否加了索引,posts.user_id 必须有索引,否则 IN 查询会全表扫描。用 EXPLAIN 看预加载语句的执行计划,重点看 type 是不是 ref 或 range,别是 ALL。第二,Blade 模板中慎用$loop 魔法属性,$loop->remaining、$loop->last 这类属性会强制 Laravel 预读整个集合,对上千条记录的 Collection 直接卡住页面渲染。真正需要判断"是不是最后一个",用 PHP 原生方式:@if($index===count($items) - 1)。第三,生产环境应固化配置与路由缓存,频繁执行 php artisan config:clear 和 route:clear 会让所有请求都去重新编译配置/路由文件,导致 CPU 尖刺、首字节延迟 (TTFB) 飙升。第四,MySQL 5.7+ 对长 IN 列表有优化,但超过几千个 ID 仍建议分批,Laravel 的 chunkById() 可配合使用。
参考来源
来源:Laravel 性能优化教程 - Laravel 如何优化性能_Laravel 应用加速常见技巧(2026 年 4 月 26 日)
来源:Laravel 数据库查询优化心得 - Laravel 怎么优化数据库查询_Laravel 如何解决 N+1 问题(2026 年 3 月 30 日)
来源:Eloquent ORM 关联查询技巧 - 避免 N+1 查询:EloquentORM 关联查询的优化技巧(2026 年 4 月 21 日)
来源:Laravel 懒加载优化方法 - Laravel 框架 lazy 怎么用_Laravel 框架懒加载查询优化方法(2026 年 4 月 24 日)