外连接消除底层原理:国产化金仓 KES 优化器设计思路拆解 📅 2026/7/22 2:55:35 很多开发人员第一次遇到“外连接消除”时都会有一种直觉上的困惑SQL 里明明写的是LEFT JOIN为什么查询结果却像INNER JOIN再看执行计划原本的外连接似乎真的被优化器改成了内连接。这究竟是优化器自作主张还是 SQL 本身就允许这样的转换答案在于两个关键词Nullable-Side 条件和逻辑等价。理解了这两个概念也就理解了金仓 KES 优化器为什么能够消除某些外连接以及为什么面对IS NULL时它又必须停下来。本文不从晦涩的优化器术语讲起而是从一条常见 SQL 入手逐层拆开外连接消除背后的判断过程。一、明明写了 LEFT JOIN左表数据为什么还是“丢了”假设有两张表t1是左表t2右表。业务希望查询t1的全部记录同时关联t2中name2为cc的信息。不少人会顺手写成下面这样SELECT*FROMt1LEFTJOINt2ONt1.id1t2.id2WHEREt2.name2cc;从开发者的意图看这条 SQL 似乎是在表达保留t1的全部数据如果t2能够匹配并且name2cc就把右表信息带出来如果不能匹配右表列显示为NULL。但它的实际语义并非如此。最终返回的只会是t1与t2成功匹配并且t2.name2cc的记录。那些没有匹配到t2的左表行会在后续过滤中被排除。对应到执行计划原本的 Outer Join 也可能被优化器转换为 Inner Join。于是表面上看是“优化器把数据优化没了”实际上却是WHERE条件改变了最终结果集。二、先看清 Nullable-Side外连接中的“可补空侧”在t1 LEFT JOIN t2中左表t1是需要被保留的一侧右表t2则是 Nullable-Side也就是可能被补成NULL的一侧。当t1中某一行在t2中找不到满足连接条件的记录时LEFT JOIN 不会丢弃这行数据而是保留t1的值并将结果中属于t2的各列填充为NULL。这正是外连接区别于内连接的核心价值。问题出在 JOIN 完成之后。从 SQL 的逻辑语义看WHERE是对连接结果进行筛选。对于右表未匹配的行此时t2.name2已经是NULL。再计算下面这个条件WHEREt2.name2cc按照 SQL 的空值逻辑NULL cc的结果不是 True而是 Unknown。这样的行不能通过WHERE筛选因此LEFT JOIN 为了保留左表数据而生成的补空行最终又被全部过滤掉了。整个过程可以概括为三步LEFT JOIN尝试按照t1.id1 t2.id2进行匹配没有匹配到右表的左表行仍然保留右表列补为NULLWHERE t2.name2 cc排除所有右表补空行。走到第三步以后外连接特有的结果已经不存在。此时使用外连接还是内连接得到的最终结果完全相同。三、外连接消除的底层依据不是“猜”而是等价变换数据库优化器的职责是在不改变查询结果的前提下寻找代价更低的执行方式。外连接消除正是这一原则的典型体现。当金仓 KES 优化器发现 Nullable-Side 上存在一个会排除补空行的WHERE条件时就具备了进行等价变换检查的基础。以上面的 SQL 为例可以把优化器的判断思路理解为LEFT JOIN 产生的右表补空行能否通过最终过滤如果不能那么外连接“保留未匹配左表行”的能力是否还会影响结果如果不会能否使用更直接、代价更低的内连接执行路径只要答案能够证明“外连接 过滤”和“内连接 过滤”在结果上等价优化器就可以消除这个外连接。这里有一个很重要的边界优化器改变的是执行方式不是 SQL 的结果语义。SQL 中虽然写着LEFT JOIN但后面的过滤条件已经让它在结果上等价于内连接。KES 所做的是提前识别这种等价关系避免先生成一批注定要被过滤的补空行再进入后续处理。从参考场景中可以归纳出 KES 这类优化设计的三个基本考量第一正确性先于性能。只有在结果严格等价时外连接才有被消除的空间。第二条件所在的一侧很关键。优化器关注的不是“有没有 WHERE”而是这个条件是否作用于 Nullable-Side以及它是否会拒绝外连接生成的NULL。第三等价之后再选择更优路径。当外连接已经失去保留补空行的实际作用时使用内连接可以缩短处理路径并为优化器选择更低代价的连接方式创造条件。这也是理解优化器的一个通用视角执行计划不需要机械复刻 SQL 的书写形式它需要忠实实现 SQL 的逻辑结果。四、为什么遇到 IS NULL优化器就不能继续消除并不是所有作用于 Nullable-Side 的条件都会触发外连接消除。最典型的例外就是IS NULL。SELECT*FROMt1LEFTJOINt2ONt1.id1t2.id2WHEREt2.name2ISNULL;这条 SQL 与前面的等值过滤有本质区别。前面的t2.name2 cc会排斥外连接生成的NULL这里的t2.name2 IS NULL恰恰是在捕捉这些空值。换句话说业务此时关心的正是右表缺失或结果为空的记录外连接生成的补空行不再是中间过程中的“无用数据”而是查询结果所依赖的对象。如果强行把这条 SQL 改成内连接右表没有匹配项的记录从一开始就不会进入结果集后续自然也无从判断IS NULL。转换前后的结果不再等价因此 KES 优化器不能进行同样的外连接消除。这恰好反向说明了外连接消除的安全边界是否消除不取决于某条固定语法规则而取决于外连接生成的NULL是否仍可能影响最终结果。五、ON 和 WHERE只差一个位置表达的却是两层语义如果业务真正想要的是“保留t1全部记录只关联t2中name2cc的数据”过滤条件就不应该放在WHERE中而应该写进ON子句SELECT*FROMt1LEFTJOINt2ONt1.id1t2.id2ANDt2.name2cc;这种写法表达的是先按name2cc限定右表参与匹配的数据再与t1进行外连接。即使某条t1记录找不到符合条件的t2它仍然会被保留只是对应的右表列显示为NULL。两种写法看起来只是移动了一个条件业务含义却完全不同条件位置实际作用对左表未匹配行的影响ON ... AND t2.name2cc规定右表哪些数据可以参与连接左表行仍保留右表列补NULLWHERE t2.name2cc对连接完成后的结果进行筛选右表补空行被过滤WHERE t2.name2 IS NULL从最终结果中查找右表为空的记录依赖外连接补空行不能按同样逻辑消除可以用一句话记住这一区别ON 控制“怎么连”WHERE 控制“连接之后留下谁”。这不是单纯的性能写法差异而是业务语义差异。把条件放对位置比事后怀疑优化器更重要。六、左表条件也要分清放在 WHERE 与放在 ON 并不等价参考场景还给出了另一个容易混淆的点如果过滤条件作用于非空侧也就是左表t1情况会怎样例如WHEREt1.name1a这个条件表示只让t1中满足name1a的记录进入最终结果然后对这些记录保留外连接语义。它属于正常的左表业务过滤不会像 Nullable-Side 的等值条件那样使外连接因为补空行被全部排除而失去意义。但如果把左表条件写进ON含义同样会发生变化t1的全部记录仍会返回只是不满足条件的左表行不参与右表匹配。可见“条件放在 ON 还是 WHERE”不能只按表的左右位置机械判断最终仍要回到业务到底希望保留哪些行。七、KES 兼容 Oracle()语法时同样遵守这套逻辑金仓 KES 支持 Oracle 的()外连接语法。在这类迁移 SQL 中外连接消除仍然可能发生判断重点依然是过滤条件是否属于外连接的一部分。如果()位于WHERE子句的连接条件中而针对 Nullable-Side 的过滤条件没有带()那么这个过滤仍可能排除外连接生成的补空行进而满足外连接消除的条件。如果对应的过滤条件也带上()其语义等同于把过滤条件放进 ANSI JOIN 的ON子句外连接特性会被保留也就不能按前面的等价关系消除。这对 Oracle 迁移到 KES 的场景尤其重要。不能只确认()语法是否能够执行还要确认每一个右表过滤条件是否被正确纳入外连接语义。少写一个()可能不会报语法错误却会悄悄改变最终保留的数据。八、从执行计划发现问题先审语义再谈性能排查涉及 OUTER JOIN 的慢 SQL 或结果异常时执行计划是重要线索。如果 SQL 文本中写的是 LEFT JOIN而计划中呈现出的连接已经具有内连接特征就需要回到 Nullable-Side 的过滤条件逐项检查。尤其要关注以下问题WHERE中是否存在右表等值过滤例如t2.name2cc该条件是否会把右表未匹配时产生的NULL全部排除业务原意究竟是筛掉未匹配行还是保留左表并让右表显示NULL如果业务要保留左表相关右表过滤是否应该移动到ON中Oracle()迁移语句中的过滤条件是否完整保留了外连接标记。审计计划时不能只关注采用了 Hash Join 还是 Nested Loop还要确认计划中呈现的连接性质。连接算法与连接语义不是同一个维度真正需要核对的是SQL 中定义的 Left Join 是否已经按内连接语义执行。如果同时伴随结果集行数异常就更要检查是否发生了非预期的外连接消除。不过看到外连接被消除并不等于看到一个优化器错误。首先要确认 SQL 的真实逻辑语义。如果WHERE本来就排除了全部补空行那么转换成内连接正是优化器在保证结果一致的前提下进行的合理选择。九、国产化迁移场景下逻辑一致性应放在第一位外连接消除本身是一项性能优化但在数据库迁移中它常常以“结果为什么少了”的形式暴露出来。原因通常不在于 KES 随意改变业务数据而在于原 SQL 对 ON、WHERE 或()的使用存在容易被忽略的语义差别当优化器将这种语义通过执行计划明确表现出来时问题才被注意到。因此迁移到金仓 KES 时不能只做语法兼容和 SQL 能否执行的检查还应对外连接语句进行结果语义校验。特别是 Nullable-Side 上的过滤条件要逐条确认它们究竟是在定义连接规则还是在筛选最终结果。对于业务系统而言优先级应当非常明确先保证与原系统的结果逻辑一致再讨论外连接是否被消除、执行路径是否更快。优化器可以改变路径但不能替业务决定哪些数据应该被保留。结语外连接消除并不神秘。它的核心只有一条如果某个过滤条件注定会排除外连接生成的全部补空行那么外连接与内连接在最终结果上已经等价优化器便可以选择更低代价的内连接路径。反过来只要最终结果仍然依赖这些补空行例如使用IS NULL查找右表缺失记录外连接就不能按同样逻辑被消除。理解这一点后再看金仓 KES 的执行计划就不会把外连接消除简单理解成“优化器改写了我的业务”。真正需要审视的是 SQL 自己表达了什么右表条件写在ON中是限制如何匹配写在WHERE中是决定哪些连接结果能够留下。很多所谓的“数据丢失”并不是数据真的消失了而是 SQL 在逻辑上从未要求保留它。把 Nullable-Side 条件放到正确的位置既是写对外连接的关键也是让 KES 优化器准确理解业务意图的前提。