eloquent 推荐用 with() 预加载替代 join,避免 n+1;需正确定义关联、包含外键、正确使用闭包筛选;必须 join 时用 join() 并注意表名、别名、前缀;复杂查询可用 db::raw() 或原生 sql;性能问题优先查执行计划并加索引。

用 Eloquent 的 with() 和关联关系做预加载,不是写原生 JOIN
Laravel 5.5 默认不鼓励手写 JOIN,而是靠模型关联 + with() 避免 N+1。比如查用户及其订单:User::with('orders')->get() 会发两条 SQL(一条查 users,一条用 WHERE user_id IN (...) 查 orders),而不是一条 LEFT JOIN。这在数据量不大、关联表有索引时足够快,且代码更易维护。
容易踩的坑:
- 忘记在模型里定义 orders() 关联方法,或返回类型写错(该用 hasMany 写成 belongsTo)
- 在闭包里用 select() 时没包含外键字段(如 user_id),导致关联失败
- 对 with() 做条件筛选却用了错误语法,比如 with(['orders' => function ($q) { $q->where('status', 'paid'); }]) 是对的,但 with(['orders.status = paid']) 会报错
真要 JOIN?用 join() 或 leftJoin() 搭配 select()
当必须单条 SQL 返回结果(比如分页需按关联字段排序)、或关联数据需参与 WHERE 条件过滤时,就得上 join()。注意 Eloquent 查询构造器的 join() 不走模型事件和访问器,只返回原始数组或集合。
实操要点:
- 表名必须用完整名('orders'),别用模型名(Order::class)
- 字段别名冲突时,显式用 select('users.name as user_name', 'orders.total as order_total')
- 多表 JOIN 时,on() 的第二个参数要加表前缀,例如 on('orders.user_id', '=', 'users.id'),漏掉引号或写成 users.id 会解析失败
- where() 条件若涉及关联表字段,得确保该表已在前面 join() 过,否则报 Unknown column
DB::table('users')
->join('orders', 'orders.user_id', '=', 'users.id')
->select('users.name', 'orders.total', 'orders.created_at')
->where('orders.status', 'shipped')
->orderBy('orders.created_at', 'desc')
->paginate(15);
复杂查询别硬套 Eloquent,直接用 DB::raw() 或原生 SQL
Laravel 5.5 的 Query Builder 对嵌套子查询、窗口函数、UNION 等支持有限。比如要查“每个用户最新一笔订单”,JOIN 很难干净实现,这时不如用 DB::raw() 包裹子查询,或干脆用 DB::select() 执行原生语句。
关键提醒:
- DB::raw() 里的内容完全不转义,拼接变量务必用 DB::raw('?') 占位符 + 参数绑定,否则 SQL 注入
- 原生查询返回的是 stdClass 对象数组,不是 Eloquent 模型,不能调 ->save() 或访问器
- 若必须返回模型实例,可用 new User($row) 手动构造,但关联关系、日期转换等不会自动触发
关联查询性能差?先看执行计划,再决定是否加索引或改写
慢查询往往不是写法问题,而是缺索引。用 DB::enableQueryLog() + dd(DB::getQueryLog()) 拿到 SQL,复制到数据库客户端执行 EXPLAIN。重点关注 type 是否为 ALL(全表扫描)、key 是否命中索引。
常见修复动作:
- 关联字段(如 orders.user_id)没建索引 → 加 INDEX
- ORDER BY 字段不在联合索引最左前缀 → 调整索引顺序或新增覆盖索引
- LIKE '%xxx' 导致索引失效 → 改用全文索引或应用层过滤
- 分页深度大(OFFSET 10000)→ 改用游标分页(记录上一页最大 ID)
别一上来就优化 SQL 写法,先确认瓶颈在哪。很多“联合查询慢”的问题,加个索引就解决了。











