应先聚合再JOIN:对右表提前按关联字段分组汇总,再与主表连接,避免笛卡尔积和中间结果集爆炸;标量子查询需替换为预聚合LEFT JOIN,LATERAL适用于依赖左表字段的动态聚合场景。

LEFT JOIN 前先聚合,别让大表拖着GROUP BY跑
直接在 LEFT JOIN 后写 GROUP BY,尤其是主表数据量大、右表关联行多时,MySQL 会先做笛卡尔积再分组,内存和临时表压力陡增。正确做法是把右表的聚合逻辑提前收口成一个结果集,再与主表连接。
比如要查每个用户的订单总金额和最新评价分数:
WITH user_orders AS ( SELECT user_id, SUM(amount) AS total_amount FROM orders WHERE status = 'paid' GROUP BY user_id ), user_reviews AS ( SELECT user_id, MAX(score) AS max_score FROM reviews GROUP BY user_id ) SELECT u.id, u.name, COALESCE(o.total_amount, 0), COALESCE(r.max_score, 0) FROM users u LEFT JOIN user_orders o ON u.id = o.user_id LEFT JOIN user_reviews r ON u.id = r.user_id;
- 聚合子查询必须带
WHERE过滤(如status = 'paid'),否则等于全表扫描 -
GROUP BY字段必须是连接键(如user_id),否则LEFT JOIN无法对齐 - 避免在聚合子查询里
SELECT *,只取后续需要的字段,减少回表和传输开销
标量子查询(SELECT ... FROM)必须替掉
类似 (SELECT COUNT(*) FROM logs WHERE user_id = u.id) 这种写法,在主表有 10 万行时,就会执行 10 万次子查询——即使加了索引,I/O 和上下文切换成本也极高。
换成 LEFT JOIN + 预聚合后,变成单次扫描+哈希匹配,耗时从秒级降到毫秒级。
- 标量子查询不能走
EXPLAIN的type=ref,它被当成“依赖型执行”,优化器几乎不干预 - 若右表过滤条件含函数(如
DATE(created_at) = '2026-09-01'),标量子查询更难命中索引 - 聚合结果为
NULL时,标量子查询天然返回NULL,但LEFT JOIN后需显式用COALESCE()补零,这点容易漏
ON 条件里收窄右表,别等 WHERE 过滤
把本该在右表做的过滤写进 WHERE,会让 LEFT JOIN 实际退化成 INNER JOIN,还导致右表无法提前裁剪。
错误写法:
SELECT u.name, o.total_amount FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'paid'; -- ❌ 这会排除所有无订单或订单非 paid 的用户
正确写法:
SELECT u.name, o.total_amount FROM users u LEFT JOIN ( SELECT user_id, SUM(amount) AS total_amount FROM orders WHERE status = 'paid' -- ✅ 放在子查询内部 GROUP BY user_id ) o ON u.id = o.user_id;
-
ON只控制连接逻辑,WHERE控制最终输出行;混淆二者是 LEFT JOIN 最常见语义错误 - 如果右表聚合后仍需按某字段排序取 Top 1(如最新订单),务必在子查询里用
ORDER BY ... LIMIT 1,且该字段要有联合索引(如(user_id, created_at))
LATERAL 是 MySQL 8.0.14+ 的真实解药
当右表聚合依赖左表多个字段(比如“每个商品在每个城市的最低价”),或者需要动态参数(如“每条订单的最近一次物流状态”),普通子查询或预聚合都难表达,这时 LATERAL 就不是可选项,而是必须项。
示例:
SELECT p.name, l.tracking_no, l.status FROM products p LEFT JOIN LATERAL ( SELECT tracking_no, status FROM logistics WHERE product_id = p.id AND city_code = p.default_city ORDER BY update_time DESC LIMIT 1 ) l ON TRUE;
-
LATERAL子查询能引用左表字段,且按需执行——没匹配就不查,避免全量扫描 - 必须配合
ORDER BY + LIMIT和对应索引(如(product_id, city_code, update_time)),否则仍可能慢 - MySQL 8.0.14 以下版本不支持,强行用会报错
ERROR 1064,别指望降级兼容
复杂点在于:LATERAL 不是语法糖,它改变了执行计划生成逻辑;一旦嵌套过深或右表无有效索引,反而比预聚合更慢。上线前必须看 EXPLAIN FORMAT=TREE 确认是否走了“LATERAL DERIVED”节点。

















