KES数据库优化踩坑指南:外连接消除引发的LEFT JOIN数据丢失问题深度剖析

 更新时间:2026年07月10日 09:33:36   作者:xcLeigh  
在使用数据库进行查询优化时,特别是在处理外连接(LEFT JOIN)时,确实可能会遇到一些常见的问题,其中之一就是由于优化不当导致使用外连接(LEFT JOIN)时数据丢失,下面我将详细解析这个问题,并提供一些避免此类问题的策略

一、前言

平时做国产库迁移,或是线上SQL调优的时候,不少开发同事都会碰到一种很难看懂的异常现象。我们明明写了LEFT JOIN,本意是把左表里所有数据都查出来,最后返回的记录却少了一大截。

拿执行计划去核对的时候就能发现,原来的左外连接,被优化器悄悄改成了INNER JOIN。这个现象,行业里一般叫做外连接消除。

KES内核自带和Oracle对齐的等价改写逻辑,要是不清楚这套底层转换规则,线上业务很容易出现数据统计出错的问题。

下面我准备了可以直接在KES里跑的测试SQL,搭配执行计划对比,把触发条件、背后逻辑、规避办法全部讲清楚,适配国产化迁移、日常SQL优化这类场景。

先搭好两张测试表用来复现问题,直接复制执行就行:

-- 创建左表t1
CREATE TABLE t1 (
    id1 INT PRIMARY KEY,
    name1 VARCHAR(20)
);
-- 创建右表t2
CREATE TABLE t2 (
    id2 INT PRIMARY KEY,
    name2 VARCHAR(20)
);
-- 插入测试数据
INSERT INTO t1 VALUES (1, '张三'),(2, '李四'),(3, '王五'),(4, '赵六');
INSERT INTO t2 VALUES (1, 'cc'),(2, 'dd');
-- 查看原始数据
SELECT * FROM t1;
SELECT * FROM t2;

原始数据展示
t1表里面一共四条记录:

id1name1
1张三
2李四
3王五
4赵六
t2表只有两条匹配数据:
id2name2
------------
1cc
2dd
本次业务需求也很简单,取出t1全部四条数据,关联匹配t2里name2等于cc的内容,没有匹配的行,t2字段显示NULL就可以。

二、踩坑案例:错误写法触发外连接消除

1. 有问题的SQL写法,过滤条件放在WHERE子句

SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2
WHERE t2.name2 = 'cc';

大家心里预期的查询结果应该是四条,id1等于1的行带出cc,剩下三条t2字段全部为空:

id1name1id2name2
1张三1cc
2李四NULLNULL
3王五NULLNULL
4赵六NULLNULL
但实际在KES里跑出来,只会返回单条数据,另外三条直接消失了:
id1name1id2name2
------------------------
1张三1cc

我们用EXPLAIN ANALYZE看执行计划,就能确认优化器做了转换:

EXPLAIN ANALYZE
SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2
WHERE t2.name2 = 'cc';

打印出来的计划关键片段如下:

Hash Join (Inner Join)
  Hash Cond: (t1.id1 = t2.id2)
  -> Seq Scan on t1
  -> Hash
       -> Seq Scan on t2
            Filter: (name2 = 'cc'::character varying)

这里能清楚看到,算子变成了内连接Hash Join,也就是前面说的外连接消除。那些右表匹配为空的数据,直接被过滤丢掉了。

三、底层原理:WHERE写右表条件会触发消除的原因

1 SQL语句实际执行的先后顺序

标准SQL的执行顺序是先执行FROM和JOIN关联,之后才会走WHERE过滤。

  • 第一步执行LEFT JOIN之后,t1里面id3、id4这两行,在t2这边没有匹配项,t2所有字段都会填充NULL;
  • 第二步走到WHERE t2.name2 = 'cc’这一段,数据库判断NULL和任意常量对比,结果都不成立,这两行就直接被舍弃;
  • 整条语句最终的执行效果,和先做内连接再过滤没有区别。

2 优化器的等价转换逻辑

KES优化器会自动做等价判断,这里的逻辑很直白:

  • WHERE条件里面,如果是针对右表的等值、范围过滤,所有NULL行最后都会被筛掉。
  • 这种场景下LEFT JOIN和INNER JOIN输出的数据完全一致。
  • 内连接的计算开销会更低,不用额外保留空行,优化器就会自动替换连接类型,也就是外连接消除。

四、不会触发外连接消除的特殊场景

只有WHERE条件写IS NULL、IS NOT NULL这类判断的时候,优化器不会改动LEFT JOIN。

举一段查询左表无匹配记录的示例SQL:

SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2
WHERE t2.name2 IS NULL;

这条语句执行出来的结果:

id1name1id2name2
2李四2dd
3王五NULLNULL
4NULLNULL

对应的执行计划也能看到保留了左连接算子:

Hash Left Join
  Hash Cond: (t1.id1 = t2.id2)
  -> Seq Scan on t1
  -> Hash
       -> Seq Scan on t2
Filter: (t2.name2 IS NULL)

原因也很好理解,IS NULL本身就是用来抓取关联后空行的逻辑,要是转成内连接,这部分数据直接就没了,优化器不会做这种转换。

五、KES里三种标准解决办法,规避数据丢失

方案1 把右表过滤条件挪到ON后面(优先推荐)

整体思路调整为先过滤右表数据,再执行左关联,左表所有记录都会保留,不会触发消除:

SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 AND t2.name2 = 'cc';

执行出来的结果符合业务预期,四条数据全部存在:

id1name1id2name2
1张三1cc
2李四NULLNULL
3王五NULLNULL
4NULLNULL

执行计划也能看到Left Join算子保留完整:

Hash Left Join
  Hash Cond: (t1.id1 = t2.id2)
  -> Seq Scan on t1
  -> Hash
       -> Seq Scan on t2
            Filter: (name2 = 'cc'::character varying)

2 Oracle兼容(+)语法场景专用写法

KES支持Oracle老式(+)左连接语法,这里也容易踩同类坑。
错误写法,过滤条件不带(+),会触发外连接消除:

SELECT * FROM t1, t2
WHERE t1.id1 = t2.id2(+) AND t2.name2 = 'cc';

正确写法,右表字段同步加上(+),过滤逻辑下沉到关联阶段:

SELECT * FROM t1, t2
WHERE t1.id1 = t2.id2(+) AND t2.name2(+) = 'cc';

3 过滤条件放在左表WHERE不受影响

如果WHERE里面过滤的是左表字段,不会触发外连接消除,只会提前筛左表原始数据:

-- 只筛选t1里姓名等于张三的数据,左关联特性不会变
SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2
WHERE t1.name1 = '张三';

六、线上SQL排查、核对小技巧

平时调优、迁移排查碰到LEFT JOIN行数不对,可以按下面步骤定位问题:

  • 1 先看执行计划里的连接算子名称
    正常保留左连接: Hash Left Join / Nested Loop Left Join
    出现消除: 只写Hash Join、Nested Loop,不带Left标识
  • 2 SQL书写规范简单整理
    右表的等值、区间、模糊匹配过滤,统一写到ON子句;
    右表判断空、非空,WHERE里面写没问题;
    左表的过滤条件,WHERE或者ON里写都可以。
  • 3 迁移阶段优先保证数据准确
    从Oracle往KES迁移的时候,不要单纯依赖优化器自动改写,数据正确比查询速度更重要。

七、全文总结

  • 1 触发外连接消除的核心原因:WHERE子句对LEFT JOIN的右表做非空类等值、范围过滤,NULL行会被全部过滤,优化器自动换成内连接。
  • 2 唯一不会触发转换的情况:WHERE条件使用IS NULL / IS NOT NULL判断右表字段。
  • 3 最稳妥的修复方式:把右表的过滤条件移动到JOIN后面的ON条件中。
  • 4 使用Oracle(+)兼容语法的时候,右表过滤字段也要同步带上(+)标识。
  • 5 线上排查异常数据,可以直接拿EXPLAIN ANALYZE查看连接算子,快速确认是否发生外连接消除。

到此这篇关于KES数据库优化踩坑指南:外连接消除引发的LEFT JOIN数据丢失问题深度剖析的文章就介绍到这了,更多相关KES数据库实战避坑内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SQL注入的四种防御方法总结

    SQL注入的四种防御方法总结

    SQL Injection是一种常见的Web安全漏洞,主要形成的原因是在数据交互中,下面这篇文章主要给大家介绍了关于SQL注入的四种防御方法,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-07-07
  • Kafka高效读写数据的原因及如何提升Kafka的吞吐量

    Kafka高效读写数据的原因及如何提升Kafka的吞吐量

    本文主要介绍了Kafka的高效读写数据的原因及如何提升Kafka的吞吐量,Kafka通过分区、稀疏索引等零拷贝等技术提高了读写效率,增加缓冲区大小、调整batch、linger等参数可以提升生产者端的吞吐量,调整fetch.max.size等max.poll.records等参数可以提升消费者端的吞吐量
    2026-05-05
  • PostgreSQL数据库服务端监听设置及客户端连接方法教程

    PostgreSQL数据库服务端监听设置及客户端连接方法教程

    这篇文章主要介绍了PostgreSQL数据库服务端监听设置及客户端连接方法,需要的朋友可以参考下
    2014-07-07
  • neo4j安装配置入门教程

    neo4j安装配置入门教程

    这篇文章主要为大家详细介绍了neo4j安装配置入门教程,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-04-04
  • neo4j实现创建多个数据库

    neo4j实现创建多个数据库

    这篇文章主要介绍了neo4j实现创建多个数据库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • Clickhouse数据表、数据分区partition的基本操作代码

    Clickhouse数据表、数据分区partition的基本操作代码

    clickhouse的分区是指将数据按照分区键进行划分,每个分区可以包含多个数据块,这篇文章主要介绍了Clickhouse数据表、数据分区partition的基本操作代码,需要的朋友可以参考下
    2023-11-11
  • 关于SQL注入中文件读写的方法总结

    关于SQL注入中文件读写的方法总结

    这篇文章主要给大家介绍了关于SQL注入中文件的读写方法,文中通过示例代码介绍的非常详细,相信对大家具有一定的参考价值,需要的朋友们下面来一起看看吧。
    2017-03-03
  • 问个高难度的复杂查询(在一个时间段内的间隔查询)

    问个高难度的复杂查询(在一个时间段内的间隔查询)

    问个高难度的复杂查询(在一个时间段内的间隔查询)...
    2007-04-04
  • SQLMesh 模型管理指南从创建到验证的实践记录

    SQLMesh 模型管理指南从创建到验证的实践记录

    本文全面介绍SQLMesh这一现代化数据转换工具的核心功能,重点讲解模型创建、编辑、验证和删除的全生命周期管理方法,通过具体示例和最佳实践,帮助数据工程师掌握SQLMesh的高效工作流程,感兴趣的朋友一起看看吧
    2025-05-05
  • sql注入之手工注入示例详解

    sql注入之手工注入示例详解

    之前和大家分享了基本的SQL注入的知识,这一篇讲的就是在得知注入点的之后,如何有效地进行脱裤。文章通过示例介绍的很详细,对大家的理解和学习很有帮助,下面来一起看看吧。
    2016-09-09

最新评论