with() 不一定提升效率,需匹配场景、精简字段、确保索引到位;主模型超100行或关联记录超10k时易触发多次in查询;应优先用wherehas、withcount、显式字段选择、join或hasmanythrough等优化手段。

直接用 with() 不一定提升效率,反而可能拖慢接口——关键看是否匹配场景、字段是否精简、索引是否到位。
with() 什么时候该用,什么时候该砍
主模型数量少(with() 才是省事又安全的选择。比如后台用户列表页加载头像和角色名:User::with(['avatar', 'role'])->get()。
- 主模型超过 100 行,或关联表记录数 > 10k,
with()很可能触发多次IN查询,而中间表缺索引时会直接type=ALL - 想查“有发布文章的用户”,别先
with('posts')再过滤,而是用whereHas('posts', fn($q) => $q->where('status', 'published'))先筛主模型 - 只统计数量?用
withCount('posts'),比with('posts')内存开销低一个数量级
字段不选干净,预加载等于白做
with('user') 默认查 users.*,哪怕你只显示 name 和 avatar_url,也会把 password_hash、remember_token 等字段一并拉回来。
- 必须显式指定字段:
with(['user:id,name,avatar_url']),漏掉id就无法完成对象绑定,$post->user始终为null - 嵌套层级每层都要带 ID:
with('comments.author:id,name'),否则comments能加载,author就断链 - 多对多中间表字段不能混进关联字段里,
with('tags:id,name,taggable_id')是错的——taggable_id属于中间表,不是tags表字段
深层嵌套(≥3 层)优先考虑反查或 join
Company::with('addresses.jobDetails.job') 看似简洁,实际发 4 条 SQL,每条都依赖前一级的 ID 列表。一旦 job_details.address_id 没索引,第二级查询就全表扫描。
- 若目标只是“某公司的所有职位”,直接从
JobDetail出发:JobDetail::whereHas('address.company', fn($q) => $q->where('id', $companyId))->with('job') - 需要扁平化字段(如
company.name+job.title),用join()更稳:JobDetail::join('addresses', 'job_details.address_id', '=', 'addresses.id')->join('companies', 'addresses.company_id', '=', 'companies.id')->select('companies.name', 'job_details.title') -
hasManyThrough能定义的路径(如User→Post→Comment),就别硬套with('posts.comments'),定义好关系后直接user->comments即可
容易被忽略的底层细节
很多人调了 with() 就以为万事大吉,但真正卡住性能的往往是数据库层面的静默问题。
- 执行前加
DB::enableQueryLog(),确认生成的 SQL 条数和内容;再用DB::select('EXPLAIN ...')看type是否为ALL或index - 多态关联(如
Comment的commentable)必须建联合索引:['commentable_type', 'commentable_id'],单列索引无效 -
load()是延迟加载,但写在循环里仍是 N+1;写在集合后如$users->load('profile')虽是单次查询,若$users已含上千条记录,PHP 内存立刻吃紧











