Laravel嵌套查询性能问题需用EXPLAIN分析执行计划:子查询和with()调用explain()可得主查与预加载语句的独立计划,但深层JOIN需手动拼SQL并EXPLAIN;orWhere分组须拆解条件单独测试索引命中;hasManyThrough等深度关联要先查实际SQL类型,再对JOIN语句EXPLAIN,重点检查type、key和Extra字段。

当你在 Laravel 中遇到嵌套查询(如子查询、with 预加载、orWhere 分组、hasManyThrough 跨表)响应异常缓慢时,不能只盯着 PHP 代码优化,必须穿透 Eloquent 层,直接查看 MySQL 实际执行路径——因为嵌套结构极易触发隐式全表扫描或临时表排序,而这些在日志里看不到,只有 EXPLAIN 能说清。
用 explain() 分析子查询和 with() 预加载
在控制器或 Tinker 中构造目标查询后,直接链式调用 ->explain() 即可获取执行计划数组。
DB::table('orders')->where('status', 'shipped')->with(['user.profile'])->explain()->get();
这行代码会生成两条 EXPLAIN:一条对应主查询 orders 表,另一条对应预加载的 user 和 profile 关联子查询。注意观察每条记录的 type 字段——若出现 ALL 或 index_merge,且 rows 值远超实际匹配数,说明该层关联未走索引。
⚠️关键陷阱:【with() 的 explain() 不会自动展开嵌套子查询的执行计划】,它只返回主查询 + 每个独立预加载语句的 plan,不会显示 JOIN 后的合并逻辑。要查深层 JOIN 效果,必须手动拼接完整 SQL 并用 DB::select('EXPLAIN ...') 执行。
分析 orWhere 分组嵌套的执行路径
方法一:用闭包包裹后调用 explain()
DB::table('users')->where('active', 1)->orWhere(function ($q) { $q->where('role', 'admin')->where('score', '>', 90); })->explain()->get();
方法二:提取闭包内 SQL 单独分析(更准)
先用 toSql() 看生成语句:DB::table('users')->where('active', 1)->orWhere(...)->toSql() → 得到 SELECT * FROM users WHERE active = ? OR (role = ? AND score > ?);再把 WHERE 部分拆开,用 DB::select('EXPLAIN SELECT * FROM users WHERE role = ? AND score > ?', ['admin', 90]) 单独测括号内条件是否命中索引。
这一步操作起来很简单,直接把文件拖进去就行。但务必注意:MySQL 对 OR 条件的索引使用极敏感,【单字段索引在 OR 中基本失效,必须建联合索引覆盖所有 OR 分支字段】,否则 type=ALL 必然发生。
深度嵌套关联(hasManyThrough / 多级 with)的 plan 检查流程
第一步:确认 Eloquent 是否真的生成了 JOIN 还是发了多条 SELECT
DB::enableQueryLog(); User::with('orders.products.category')->first(); dd(DB::getQueryLog()); 查看最后几条 SQL 是单条 JOIN 还是 4 条独立查询。只有 JOIN 才能用 EXPLAIN 一次性分析整条路径。
第二步:对 JOIN SQL 手动加 EXPLAIN 前缀
复制上一步查到的完整 JOIN SQL(如 SELECT users.*, products.name AS product_name FROM users LEFT JOIN orders ON ...),粘贴进 DB::select('EXPLAIN ' . $sql) 执行。
第三步:重点核对三处
① 每个 JOIN 表的 type 是否为 ref/eq_ref,而非 ALL;
② key 字段是否显示实际使用的索引名,NULL 即未命中;
③ Extra 列是否含 "Using temporary" 或 "Using filesort",出现即表示内存排序失败,必须优化 WHERE 或 ORDER BY 字段索引。


















