LEFT JOIN 的 WHERE 条件无法下推至左表,因语义要求保留左表所有行;仅非保留表(如右表)的 WHERE 条件可下推,ON 中含 NULL 或函数会导致索引失效、下推失败。

LEFT JOIN 的 WHERE 条件无法下推到左表
外连接保留表(如 LEFT JOIN 的左表)的 WHERE 过滤条件,几乎必然无法下推——因为优化器必须先保证该表所有行都进入结果,再做后续过滤。一旦你写 WHERE u.status = 'active',执行计划里往往出现对 users 表的全扫描,哪怕它有 status 索引。
真正能下推的,是明确作用于非保留表的条件:比如 LEFT JOIN orders o ON u.id = o.user_id WHERE o.amount > 100,这个 o.amount > 100 通常能下推到 orders 表扫描阶段。
- 检查执行计划:若
Estimated Number of Rows接近左表总行数,说明没下推 - 把左表过滤提前:用 CTE 或子查询先筛出
u.status = 'active'的子集,再LEFT JOIN - 别指望
STRAIGHT_JOIN或FORCE INDEX能绕过这个限制——这是语义决定的,不是优化器 bug
ON 子句里含可空列或函数时,索引失效阻断下推路径
LEFT JOIN 的 ON 条件如果涉及可空字段(如 orders.user_id 允许为 NULL),B+ 树索引不存 NULL,优化器就很难评估选择性,倾向放弃索引查找,改用哈希或嵌套循环全扫——此时谓词下推失去载体。
更常见的是在 ON 里套函数,比如 COALESCE(o.user_id, 0) = u.id 或 DATE(o.create_time) = '2025-06-01',这直接让索引失效,下推自然无从谈起。
- 把
o.user_id IS NOT NULL显式加进ON,有时能帮优化器重估选择性 - 用范围替代函数:把
DATE(o.create_time) = '2025-06-01'改成o.create_time >= '2025-06-01' AND o.create_time - 确保
user_id列统计信息最新:UPDATE STATISTICS orders (user_id)(SQL Server)或ANALYZE TABLE orders(MySQL)
Full Outer Join 在 MySQL 中根本不会触发下推
MySQL 不支持 FULL OUTER JOIN 语法,一写就报错 ERROR 1064 (42000)。你用 LEFT JOIN ... UNION ALL ... RIGHT JOIN 模拟时,每个分支都是独立查询,优化器无法跨分支做全局谓词下推。
即使你手动把过滤条件塞进两个子查询里,也得分别维护、容易漏写,且 UNION ALL 后无法再统一过滤——下推被硬生生切成两段,中间还夹着去重逻辑(如果用了 UNION)。
- 业务允许时,优先用
LEFT JOIN+RIGHT JOIN分开查,避免拼接 - 别在模拟
FULL OUTER JOIN的 SQL 里写外层WHERE——它只会作用于最终合并结果,无法下推到任一分支 - PostgreSQL 或 SQL Server 用户注意:
FULL OUTER JOIN的下推能力也极弱,基本只支持右表过滤下推
谓词下推在 Outer Join 中的边界非常窄
和 INNER JOIN 不同,OUTER JOIN 的谓词下推不是“能不能”的问题,而是“推到哪、推多少”的问题。左表的过滤几乎不能下推,右表的过滤能下推但受 ON 条件质量制约,而 ON 本身又因 NULL 处理和函数使用极易失效。
最容易被忽略的是:即使你成功把条件下推到右表,如果右表数据量仍很大(比如订单表未按时间分区),JOIN 阶段依然可能溢出到 tempdb 或触发 Sort,这时下推带来的收益就被抵消了。

















