从 LEFT JOIN 到 INNER JOIN:KES 外连接消除机制深度解析

数据库优化深度科普|外连接消除底层原理:国产化金仓 KES 优化器设计思路拆解

很多开发人员第一次遇到"外连接消除"时,都会有一种直觉上的困惑:SQL 里明明写的是 LEFT JOIN,为什么查询结果却像 INNER JOIN?再看执行计划,原本的外连接似乎真的被优化器改成了内连接。

这究竟是优化器自作主张,还是 SQL 本身就允许这样的转换?

答案在于两个关键词:Nullable-Side 条件逻辑等价 。理解了这两个概念,也就理解了金仓 KES 优化器为什么能够消除某些外连接,以及为什么面对 IS NULL 时,它又必须停下来。

本文不从晦涩的优化器术语讲起,而是从一条常见 SQL 入手,逐层拆开外连接消除背后的判断过程。

一、明明写了 LEFT JOIN,左表数据为什么还是"丢了"

假设有两张表:t1 是左表,t2 是右表。业务希望查询 t1 的全部记录,同时关联 t2name2'cc' 的信息。

不少人会顺手写成下面这样:

sql 复制代码
SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2
WHERE t2.name2 = 'cc';

从开发者的意图看,这条 SQL 似乎是在表达:保留 t1 的全部数据,如果 t2 能够匹配,并且 name2='cc',就把右表信息带出来;如果不能匹配,右表列显示为 NULL

但它的实际语义并非如此。最终返回的,只会是 t1t2 成功匹配,并且 t2.name2='cc' 的记录。那些没有匹配到 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。再计算下面这个条件:

sql 复制代码
WHERE t2.name2 = 'cc'

按照 SQL 的空值逻辑,NULL = 'cc' 的结果不是 True,而是 Unknown。这样的行不能通过 WHERE 筛选,因此,LEFT JOIN 为了保留左表数据而生成的补空行,最终又被全部过滤掉了。

整个过程可以概括为三步:

  1. LEFT JOIN 尝试按照 t1.id1 = t2.id2 进行匹配;
  2. 没有匹配到右表的左表行仍然保留,右表列补为 NULL
  3. WHERE 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

sql 复制代码
SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2
WHERE t2.name2 IS NULL;

这条 SQL 与前面的等值过滤有本质区别。

前面的 t2.name2 = 'cc' 会排斥外连接生成的 NULL;这里的 t2.name2 IS NULL 恰恰是在捕捉这些空值。换句话说,业务此时关心的正是右表缺失或结果为空的记录,外连接生成的补空行不再是中间过程中的"无用数据",而是查询结果所依赖的对象。

如果强行把这条 SQL 改成内连接,右表没有匹配项的记录从一开始就不会进入结果集,后续自然也无从判断 IS NULL。转换前后的结果不再等价,因此 KES 优化器不能进行同样的外连接消除。

这恰好反向说明了外连接消除的安全边界:是否消除,不取决于某条固定语法规则,而取决于外连接生成的 NULL 是否仍可能影响最终结果。

五、ON 和 WHERE,只差一个位置,表达的却是两层语义

如果业务真正想要的是"保留 t1 全部记录,只关联 t2name2='cc' 的数据",过滤条件就不应该放在 WHERE 中,而应该写进 ON 子句:

sql 复制代码
SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 AND t2.name2 = 'cc';

这种写法表达的是:先按 name2='cc' 限定右表参与匹配的数据,再与 t1 进行外连接。即使某条 t1 记录找不到符合条件的 t2,它仍然会被保留,只是对应的右表列显示为 NULL

两种写法看起来只是移动了一个条件,业务含义却完全不同:

条件位置 实际作用 对左表未匹配行的影响
ON ... AND t2.name2='cc' 规定右表哪些数据可以参与连接 左表行仍保留,右表列补 NULL
WHERE t2.name2='cc' 对连接完成后的结果进行筛选 右表补空行被过滤
WHERE t2.name2 IS NULL 从最终结果中查找右表为空的记录 依赖外连接补空行,不能按同样逻辑消除

可以用一句话记住这一区别:ON 控制"怎么连",WHERE 控制"连接之后留下谁"。

这不是单纯的性能写法差异,而是业务语义差异。把条件放对位置,比事后怀疑优化器更重要。

六、左表条件也要分清:放在 WHERE 与放在 ON 并不等价

参考场景还给出了另一个容易混淆的点:如果过滤条件作用于非空侧,也就是左表 t1,情况会怎样?

例如:

sql 复制代码
WHERE t1.name1 = 'a'

这个条件表示,只让 t1 中满足 name1='a' 的记录进入最终结果,然后对这些记录保留外连接语义。它属于正常的左表业务过滤,不会像 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 的过滤条件逐项检查。

尤其要关注以下问题:

  1. WHERE 中是否存在右表等值过滤,例如 t2.name2='cc'
  2. 该条件是否会把右表未匹配时产生的 NULL 全部排除;
  3. 业务原意究竟是筛掉未匹配行,还是保留左表并让右表显示 NULL
  4. 如果业务要保留左表,相关右表过滤是否应该移动到 ON 中;
  5. 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 优化器准确理解业务意图的前提。

相关推荐
云边有个稻草人1 个月前
金仓数据库KES:内核级DISTINCT语句深度优化,破解数据去重性能难题
sql 性能优化·数据库内核优化·国产数据库优化·distinct 优化·慢查询调优
梵得儿SHI2 个月前
SpringCloud 进阶拓展:性能优化指南(缓存三大问题 + 分库分表入门)
spring cloud·缓存·微服务·性能优化·高并发·分库分表·数据库优化
云边有个稻草人2 个月前
KES内核优化实战:标量子查询消除赋能SQL性能大幅跃升
标量子查询消除·sql 性能优化·金仓 kes·数据库内核优化·向量化执行
海市公约2 个月前
一条SQL查询的完整旅程:MySQL执行流程深度解析
sql·mysql·数据库优化·执行计划·连接器·查询缓存·sql执行原理
未来之窗软件服务2 个月前
数据库优化(九)随机抽选系统数据表 ——东方仙盟
大数据·数据库·数据库优化·仙盟创梦ide·东方仙盟
小白君6532 个月前
互联网大厂Java面试:从Spring Boot到微服务的技术场景深度解析
spring boot·redis·微服务·消息队列·java面试·数据库优化
fobwebs4 个月前
如何通过phpmyadmin指令来优化数据库表,给数据库“减肥”。
数据库·wordpress·数据库优化·phpmyadmin
未来之窗软件服务5 个月前
数据库优化提速(四)新加坡房产系统开发数据库表结构—仙盟创梦IDE
数据库·数据库优化·计算机软考
编程彩机6 个月前
互联网大厂Java面试:从数据库优化到微服务架构场景解析
消息队列·微服务架构·java面试·数据库优化·监控运维