正确使用 LEFT JOIN 替代子查询可提升性能,关键在于右表需提前收缩、关联字段建索引、过滤条件放 ON 而非 WHERE;LATERAL 适用于依赖左表字段的按需执行场景。

直接用 LEFT JOIN 替代子查询,通常能避免性能损耗——但前提是写法正确。错误地把子查询“硬塞”进 LEFT JOIN 反而更慢,尤其当子查询未收缩、无索引、或被误放在 WHERE 中时。
LEFT JOIN 多个子查询为什么反而更慢
MySQL 8.0+ 对嵌套子查询的重写能力有限,它不会自动把子查询“压平”成高效连接。常见退化场景:
- 子查询没加
WHERE过滤,比如(SELECT * FROM logistics),导致全表加载进内存再关联 - 子查询含
LIMIT或HAVING,优化器无法下推条件,被迫物化为临时表且无索引 -
SELECT *导致回表,即使加了WHERE,I/O 量仍远超只选关键字段 - 多个子查询并列写在
FROM后,如LEFT JOIN (SELECT ...) b ON ... LEFT JOIN (SELECT ...) c ON ...,中间结果集叠加爆炸
用 LATERAL 替代并列子查询(MySQL 8.0.14+)
当每个右表数据都依赖左表某字段(例如查每条订单的最新物流、最新评价),LATERAL 是最安全的替代方案:它让子查询按需执行,且可走索引。
正确写法示例:
SELECT o.id, l.tracking_no, r.score FROM orders o LEFT JOIN LATERAL ( SELECT tracking_no FROM logistics WHERE order_id = o.id ORDER BY update_time DESC LIMIT 1 ) l ON TRUE LEFT JOIN LATERAL ( SELECT score FROM reviews WHERE order_id = o.id ORDER BY created_at DESC LIMIT 1 ) r ON TRUE;
关键点:
-
LATERAL子查询中可直接引用o.id,无需提前物化 -
ORDER BY ... LIMIT 1配合order_id上的索引(如(order_id, update_time)),可走覆盖索引,避免排序 - 比并列子查询少一次全量扫描,也避免了
HAVING 1=1这类黑盒技巧的不确定性
ON 条件里收窄右表,别等 WHERE
把本该在右表上做的过滤,错放到 WHERE,会让 LEFT JOIN 实际变成 INNER JOIN,还拖慢速度。
错误写法:
SELECT o.order_id, c.customer_name FROM orders o LEFT JOIN customers c ON o.customer_id = c.id WHERE c.status = 'VIP'; -- 这会过滤掉所有无匹配客户或非 VIP 的订单
正确写法(保左表、减右表):
SELECT o.order_id, c.customer_name FROM orders o LEFT JOIN customers c ON o.customer_id = c.id AND c.status = 'VIP';
为什么有效:
-
ON中的c.status = 'VIP'在关联阶段就筛掉右表大量行,减少匹配次数 - 仍保留所有
orders行,无匹配则c.customer_name为NULL - 若
customers(status, id)有联合索引,可高效定位
聚合场景优先用 GROUP BY + LEFT JOIN,而非子查询
想对一对多关系取“每个主表行对应的一条副表记录”(如最新订单、最高评分),别用相关子查询——它会 N+1 执行。
低效子查询写法:
SELECT u.name, ( SELECT score FROM reviews r WHERE r.user_id = u.id ORDER BY created_at DESC LIMIT 1 ) AS latest_score FROM users u;
更稳更快的 LEFT JOIN 写法:
SELECT u.name, r.score AS latest_score FROM users u LEFT JOIN reviews r ON u.id = r.user_id LEFT JOIN reviews r2 ON u.id = r2.user_id AND r2.created_at > r.created_at WHERE r2.id IS NULL;
说明:
- 这是经典的“反向自连接找最大值”模式,避免了子查询逐行执行
- 要求
reviews(user_id, created_at)有联合索引,否则r2.created_at > r.created_at无法高效走索引 - 若数据量极大且业务允许近似,也可考虑先用
GROUP BY user_id+MAX(created_at)做一层预聚合
真正影响性能的从来不是“用不用子查询”,而是右表是否被提前收缩、关联字段有没有索引、以及条件写在 ON 还是 WHERE。哪怕用了 LATERAL,如果子查询里没索引或没限制字段,照样慢。


















