
复杂查询中基于代价的连接条件下推实践与思考
在实际的业务系统中,SQL 往往并不像教科书示例那样简洁。随着业务复杂度的提升,CTE、多层子查询、窗口函数、聚集计算被大量用于组织逻辑。然而,这类 SQL 在带来可读性的同时,也给查询优化器带来了巨大的挑战,尤其是在 JOIN 条件无法有效提前过滤数据的场景下,性能问题尤为突出。本文将围绕一个在真实客户场景中频繁出现的问题——复杂查询中 JOIN 条件下推失败导致的性能瓶颈,系统性地介绍一种基于代价模型的连接条件下推(Cost-based Join Predicate Pushdown)的设计与实现思路。
一、问题背景
1.1 客户场景中的典型痛点
在很多客户业务中,SQL 通常采用如下模式来组织逻辑:
- 在子查询或 CTE 中完成大量计算(去重、聚集、窗口函数等)
- 在外层再与其他表进行 JOIN,并施加高选择性的过滤条件
例如:

从业务语义上看,这条 SQL 没有任何问题;但从执行角度看,却隐藏着严重的性能隐患:
- 子查询 s 需要对 s1 做全量扫描并去重
- 外层 s2.b = 3 的高选择性条件,无法影响子查询的扫描范围
- 导致子查询输出一个巨大的中间结果集
- 后续 JOIN、聚集等操作都发生在'大数据量'之上,性能急剧下降
根本问题并不在 JOIN 本身,而在于过滤发生得不够早。
1.2 业界普遍面临的两大难点
将 JOIN 条件下推到子查询内部,看起来是一个直观有效的优化方向,但在数据库内核层面,这个问题远没有想象中简单,主要体现在两个方面:
1.2.1 语义安全性(Equivalence)
JOIN 条件下推,本质上是在改变谓词生效的位置。如果处理不当,很容易改变 SQL 的语义,尤其是在以下场景中:
- 聚集(GROUP BY)
- 窗口函数(Window Function)
- DISTINCT / UNION
- 含有副作用或非确定性函数的表达式
因此,不是所有 JOIN 条件都可以安全地下推,必须有严格的等价性判定。
1.2.2 代价评估(Cost)
即便在语义上等价,下推也未必'划算':
- 下推后可能触发参数化执行
- 外层基数较大时,可能导致子查询被重复执行 N 次
- 极端情况下,性能反而出现灾难性下降
这意味着:JOIN 条件下推不仅要'能推',还要'值得推'。
二、传统方案的局限
传统优化器在面对上述 SQL 时,通常会采用如下执行策略:
2.1 完整执行子查询
- 扫描基表
- 做 DISTINCT / UNION / 窗口函数等复杂操作
2.2 生成一个大的中间结果集
2.3 再与外层表进行 JOIN,并施加过滤条件
这一策略的致命问题在于:外层的高选择性 JOIN / WHERE 条件,无法反向约束子查询的扫描范围。当子查询本身计算复杂、数据量大时,这种执行路径几乎必然成为性能瓶颈。








