结论:用 leftJoin(),别用 with() 做左连接;with() 是预加载,leftJoin() 才是真·SQL 左连接。

直接说结论:用 leftJoin(),别用 with() 做左连接;with() 是预加载,leftJoin() 才是真·SQL 左连接。
为什么 leftJoin() 比 with() 更适合做左连接查询
很多人误以为 with() 能“模拟”左连接,其实它只是发两条独立 SQL(主表查一次,关联表再查一次),根本不会生成 LEFT JOIN 语句。当你需要在 WHERE 或 ORDER BY 中用到关联字段(比如按用户所在城市排序、查没订单的用户),with() 完全无能为力。
-
with()返回的是主模型集合 + 关联模型对象,字段不在同一结果集里,无法排序或条件过滤 -
leftJoin()真正把两张表拼成一张临时表,所有字段可直接参与select、where、orderBy - Yajra DataTables 等组件对
with()的关联字段排序支持极差,但对leftJoin()字段天然兼容
leftJoin() 的 on 条件必须写对,否则性能暴跌
常见错误是在 leftJoin() 的闭包里混用 where() 和 on()。Laravel 会把 where() 放到 ON 子句里,但某些数据库优化器不买账——尤其在 LEFT JOIN 场景下,会导致全表扫描。
- ✅ 正确:所有连接条件都用
on()链式调用,比如->on('posts.user_id', '=', 'users.id') - ❌ 错误:在
leftJoin()闭包里写$join->where('posts.published', 1),这会让 MySQL 无法走索引 - ⚠️ 如果真要加关联表的过滤条件(如只统计已发布文章数),应改用子查询或先用
whereHas()过滤主表
关联字段名冲突时必须用 addSelect() + 别名
用 leftJoin() 后,两张表可能都有 id、name 字段,Eloquent 默认只取主表字段,且 get() 返回的集合里同名字段会被覆盖。
- 用
addSelect()显式声明要取的关联字段,并加别名:->addSelect('posts.title as post_title', 'posts.created_at as post_created') - 避免直接
select('*'),它会让字段歧义更严重,且拖慢查询 - 如果后续要映射回模型(比如想让结果也能调用
$item->user),得手动用new Model($row)构造,leftJoin()本身不触发模型实例化
什么时候该坚持用 with(),而不是硬套 leftJoin()
leftJoin() 不是万能解药。如果你只需要展示数据、不涉及关联字段的 WHERE/ORDER/SUM,with() 更安全、更易维护。
- 需要懒加载或动态判断是否加载关联?只能用
with() - 关联表数据量极大,但你只显示前 3 条?
with()+ 约束式闭包比leftJoin()+LIMIT更可控 - 业务逻辑依赖模型事件(如
creating、updated)?with()加载的是完整模型实例,leftJoin()只是数组/StdClass
真正容易被忽略的是:leftJoin() 返回的数据不是模型实例,哪怕你用了 from('users'),它也不会自动转成 User::class 对象——这点在调用访问器(accessor)、强制类型转换或序列化时会突然报错。


















