目录
前言
一、现场现象:迁移后 LEFT JOIN 查询诡异丢数据
1. 实际业务场景
2. 沿用源库的错误 SQL 写法
业务预期结果
KES 实际运行结果
执行计划真实佐证
二、底层原理:为什么 KES 会改写 LEFT JOIN 为内连接?
2.1 SQL 执行顺序导致的隐性过滤
2.2 KES 优化器的性能优化逻辑
三、特殊豁免场景:不会触发外连接消除的情况
安全无坑示例 SQL
核心内核逻辑
四、KES 迁移实战避坑方案
方案一:右表过滤条件全部下移至 ON 子句(通用最优解)
方案二:Oracle(+) 兼容语法专属处理方式
方案三:左表 WHERE 过滤无任何丢数据风险
方案四:通过执行计划快速排查隐患 SQL
五、迁移落地总结与团队开发规范
1. 核心开发准则
2. 迁移落地优先级建议
前言
最近在负责多套 MySQL、PostgreSQL 业务系统迁移金仓 KES 的落地工作,过程中碰到了一个非常隐蔽且折磨人的问题:很多原本在源库运行正常的LEFT JOIN查询,迁移到 KES 后,业务数据莫名少了一大半。
最让人头疼的是,程序完全没有报错,SQL 语句一行没改、业务逻辑也完全没变,单纯换了数据库,结果集就对不上了。初期排查时,我们反复核对代码、校验表数据、比对业务逻辑,始终找不到问题根源,一度怀疑是数据库迁移同步数据出了问题。
后续拆解执行计划才发现,这根本不是数据同步问题,也不是 KES 数据库 Bug,而是 KES 优化器的外连接消除机制在悄悄生效。相比于 MySQL、PostgreSQL,KES 的查询优化更加激进,会自动改写外连接语句,将 LEFT JOIN 转为 INNER JOIN,最终导致业务数据丢失。
这是异构数据库迁移中非常典型的兼容性坑,大量开发、运维、DBA 都会中招。我结合本次落地踩坑经验,从现场现象、底层原因、特殊场景、实操解决方案全方位梳理,给正在做 KES 迁移的小伙伴避坑。
一、现场现象:迁移后 LEFT JOIN 查询诡异丢数据
1. 实际业务场景
为了方便大家复现和理解,我简化了线上业务模型,核心就是两张最普通的关联表,也是日常开发中最常用的一对多关联场景:
t1:主业务表(左表),存储核心业务数据,业务需求要求必须保留全量数据,无论关联附表是否有数据,主表记录都不能丢失。t2:附属信息表(右表),存储主表的拓展数据,属于可选关联,无匹配数据时,字段展示 NULL 即可。
本次业务需求很简单:查询 t1 所有数据,仅关联匹配 t2 中 name2 等于「cc」的记录,不匹配的 t1 数据正常保留,附表字段置空。
这套逻辑在 MySQL、PostgreSQL 中运行完全正常,结果符合业务预期,但迁移至 KES 后,直接出现数据缺失。
2. 沿用源库的错误 SQL 写法
很多人迁移时会直接复用原有 SQL,这也是踩坑的核心原因,下面是典型的错误写法:
-- 危险写法:将右表过滤条件写在 WHERE 子句中 SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 WHERE t2.name2 = 'cc';业务预期结果
完整返回 t1 全部数据行;t2 匹配成功且 name2 为 cc 的展示对应附表数据,未匹配的 t1 数据保留,t2 相关字段显示 NULL。
KES 实际运行结果
只返回 t1 和 t2 完全匹配、且满足 name2='cc' 的交集数据,所有无附表匹配的主表数据全部被过滤掉,相当于彻底丢失了一部分业务数据。
执行计划真实佐证
我们通过 KES 执行计划排查后发现,语句中明明写的是 LEFT JOIN,但执行计划里已经没有了左外连接算子,被自动替换成了普通的 Hash Join、Nested Loop 内连接执行逻辑。这就是 KES 优化器自动触发的外连接消除,也是数据丢失的真正原因。
二、底层原理:为什么 KES 会改写 LEFT JOIN 为内连接?
很多开发同学疑惑:明明写了左连接保数据,数据库为什么会自作主张改写法?其实这是由 SQL 执行规则和 KES 优化器的智能等价变换共同决定的,并非数据库异常。
2.1 SQL 执行顺序导致的隐性过滤
标准 SQL 的执行逻辑是先关联 JOIN,再执行 WHERE 过滤,这个顺序是所有数据库通用的,也是问题的根源:
第一步,执行 LEFT JOIN 关联后,所有 t1 数据都会保留,没有匹配 t2 的数据行,t2 所有字段都会填充为 NULL; 第二步,执行 WHERE t2.name2 = 'cc' 过滤条件,数据库判断 NULL = 'cc' 属于未知不匹配结果,会直接过滤掉这部分空值行; 最终结果就是:所有不匹配的主表数据全部被剔除,和直接使用 INNER JOIN 的查询结果完全一样。
2.2 KES 优化器的性能优化逻辑
相比于 MySQL、PG 的宽松优化策略,KES 优化器的等价校验更加严格。优化器在解析 SQL 时,会自动判断语句的逻辑等价性:如果「LEFT JOIN + WHERE 右表常规过滤」的最终结果和 INNER JOIN 完全一致,那外连接就没有任何存在意义。
为了降低查询开销、减少数据扫描和关联计算成本,KES 会主动做优化改写,直接将外连接降级为效率更高的内连接。站在数据库性能角度,这个优化是合理的,但对于迁移业务来说,就会直接打破原有业务逻辑,造成数据丢失。
三、特殊豁免场景:不会触发外连接消除的情况
这里重点提醒大家:不是所有写在 WHERE 里的右表条件都会触发优化改写,有且仅有一种特殊场景是豁免的,也是我们日常查询空数据常用的写法。
如果 WHERE 条件是右表 IS NULL 判空,KES 绝对不会执行外连接消除。
安全无坑示例 SQL
-- 安全写法:查询主表中无附表匹配的数据,不会触发外连接消除 SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 WHERE t2.name2 IS NULL;核心内核逻辑
IS NULL 的语义本身就是为了筛选 LEFT JOIN 产生的空值数据。如果优化器把这条语句改成内连接,就彻底查不到「主表无关联数据」的记录,直接导致业务功能失效。
KES 优化器能够精准识别这种业务语义,为了保证查询结果绝对准确,会主动跳过优化改写逻辑,完整保留 LEFT JOIN 特性。这也是我们排查问题、编写空值查询时需要牢记的关键点。
四、KES 迁移实战避坑方案
结合本次迁移踩坑经验,我整理了四套高频实用的解决方案,覆盖标准 SQL 写法、Oracle 兼容语法、条件区分、问题排查,适配绝大多数迁移场景。
方案一:右表过滤条件全部下移至 ON 子句(通用最优解)
这是最稳妥、兼容性最好、全数据库通用的修复方式,也是官方推荐的开发规范。
大家只要记住一个简单的判断逻辑:想要保留左表全量数据,所有针对右表的筛选条件,一律写在 ON 后面,不要写在 WHERE 后面。
调整后执行逻辑会完全改变:数据库先过滤右表有效数据,再和左表做外连接,从根源上避免空值行被过滤,彻底杜绝外连接消除问题。
-- 修复后标准写法,KES、MySQL、PG 全兼容,无数据丢失问题 SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 AND t2.name2 = 'cc'; -- 附表筛选条件放入ON子句方案二:Oracle(+) 兼容语法专属处理方式
很多老旧业务是从 Oracle 迁移至 KES,会沿用 KES 兼容的 Oracle(+) 隐式外连接语法,这也是高频踩坑点,很多人因为写法不规范导致隐性丢数据。
核心规则非常关键:使用(+)语法时,WHERE 子句中所有右表的过滤条件,必须全部带上(+),否则一定会触发外连接消除。
错误示范(丢数据):
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'; -- 右表条件统一加(+),等价ON子句过滤补充说明:(+)语法仅适配 Oracle 兼容模式,新项目建议直接使用标准 LEFT JOIN 写法,可读性和兼容性更好。
方案三:左表 WHERE 过滤无任何丢数据风险
很多同学容易混淆左右表条件的区别,这里明确说明:只有右表的常规 WHERE 过滤会触发外连接消除,针对左主表的 WHERE 过滤是绝对安全的。
左表是外连接的驱动表,对左表的过滤属于业务前置筛选,只会剔除不符合条件的主表数据,不会改变 LEFT JOIN 的外连接属性,KES 不会做任何优化改写。
-- 左表过滤安全写法,无外连接消除风险 SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 WHERE t1.name1 = 'a';方案四:通过执行计划快速排查隐患 SQL
在批量迁移上线阶段,不可能逐条人工校验逻辑,最高效的方式就是通过 KES 执行计划批量筛查问题 SQL。
给大家整理了快速判断标准:
正常 LEFT JOIN:执行计划包含 Left Join / Left Outer 算子
异常被优化改写:算子变为 Hash Join / Nested Loop,无 Left 标识,同时结果集偏少
日常排查可直接使用以下命令,快速定位异常语句:
EXPLAIN ANALYZE SELECT * FROM t1 LEFT JOIN t2 ON t1.id1 = t2.id2 WHERE t2.name2 = 'cc';五、迁移落地总结与团队开发规范
经过本次批量迁移踩坑,我们团队统一整理了适配 MySQL、PG 迁移 KES 的开发规范,规避后续批量出现同类问题,非常适合团队落地执行。
1. 核心开发准则
分清语义边界:ON 子句只用来定义表与表的关联规则,WHERE 子句只用来做最终结果的全局过滤;
规范附表过滤:除 IS NULL 空值查询外,所有右表筛选条件,统一迁移至 ON 子句;
老旧语法规范:使用 Oracle(+) 语法时,右表所有过滤条件必须统一追加(+)符号。
2. 迁移落地优先级建议
异构数据库迁移,数据一致性永远优先于性能优化。KES 的外连接消除是优秀的性能优化机制,但在迁移初期,我们必须优先对齐 MySQL、PG 的原有业务逻辑,保证数据准确无误,后续再根据业务场景做性能调优。
同时建议将 JOIN 类 SQL 纳入上线强制审计范围,只要是含 LEFT/RIGHT JOIN 的语句,必须查看执行计划,确认连接类型未被自动改写。批量迁移场景下,可通过脚本批量扫描 WHERE 子句中的右表过滤条件,统一批量整改,提升迁移效率。