国产化落地避坑 · 干货向|Oracle 迁金仓 KES,我把外连接消除排在隐性陷阱第一位

📅 发布时间:2026/7/21 15:56:03
国产化落地避坑 · 干货向|Oracle 迁金仓 KES,我把外连接消除排在隐性陷阱第一位 从 Oracle 迁到金仓 KES 这几年我攒了一份隐性陷阱清单。所谓隐性是它们不报错。语法过了程序跑了数据也出来了只是结果悄悄和 Oracle 不一样。这类坑比报错的坑难缠十倍因为报错会拦住你它不会它让你带着错误的数据一路上线。这份清单里我把外连接消除排在第一位。原因很简单它同时踩中了三件最要命的事静默、常见、跟数据正确性直接挂钩。一条你写了很多年的 LEFT JOIN迁过来行数就少了一截业务方在群里问上周的数据怎么对不上你回去翻代码SQL 一个字没改放回 Oracle 上跑还是对的。先说清楚它长什么样。一、先复现LEFT JOIN 的行数为什么少了假设有两张表t1 是左表也就是驱动表t2 是右表。需求是查出 t1 的所有记录同时把 t2 里 name2 为 ‘cc’ 的信息带出来。很多人会顺手写成这样。SELECT*FROMt1LEFTJOINt2ONt1.id1t2.id2WHEREt2.name2cc;按 LEFT JOIN 的直觉你预期的结果是t1 的所有行都在t2 匹配上且 name2 为 ‘cc’ 的显示数据匹配不上的显示 NULL。实际拿到的结果是只剩下 t1 与 t2 成功匹配、并且 t2.name2 为 ‘cc’ 的那些行。t1 里没匹配上的记录全没了。打开执行计划你会看到原本写的 Outer Join被优化器换成了 Inner Join。这就是外连接消除。你的 LEFT JOIN 在执行计划层面被降级成了 INNER JOIN。二、优化器为什么敢把外连接改成内连接这不是 bug是优化器按 SQL 语义做的一次合法变换。想明白它抓住两点就够。第一点WHERE 的过滤发生在 JOIN 之后。LEFT JOIN 先执行右表没匹配上的行t2 那一侧的列会被填成 NULL。然后 WHERE 才上场。你的条件是 t2.name2 ‘cc’而对那些填了 NULL 的行来说NULL cc的结果不是 false是 Unknown在 WHERE 里 Unknown 一样会被过滤掉。于是外连接辛苦保留下来的那些 NULL 行被 WHERE 一句话全删了。第二点优化器会做等价变换检查。它发现既然 WHERE 里这个针对右表非空列的条件注定会把外连接产生的所有 NULL 行过滤干净那么「外连接加这个过滤」的最终结果跟「内连接加这个过滤」在数学上完全一样。两条路终点相同优化器当然挑代价更低的那条也就是内连接。所以它不是算错了是你写的这条 SQL 在语义上本来就等价于一条内连接优化器只是把这层等价关系用了起来。真正的问题在于你以为你在写外连接落到语义上却给了它一条内连接。三、有一种情况它不会消除IS NULL不是所有针对右表的条件都会触发消除。最典型的例外是 IS NULL。SELECT*FROMt1LEFTJOINt2ONt1.id1t2.id2WHEREt2.name2ISNULL;这条不会被消除道理也顺。外连接的核心用途之一就是找出右表里缺失的记录而t2.name2 IS NULL恰恰是在捞这些由外连接产生的 NULL 行。这时候要是还转成内连接那些缺失记录就永远进不了结果集结果直接错。所以为了保证结果正确优化器在这种场景下不会做外连接消除。给你一个一秒判断的诀窍看你的 WHERE 到底是在排除右表的 NULL 行还是在专门捞右表的 NULL 行。前者会触发消除后者不会。四、迁移时怎么写才对原理懂了解法就清楚了核心就一句话针对右表的过滤除非你是要查空否则应该放进 ON而不是 WHERE。把过滤条件下推到 ON 子句这是正确写法。SELECT*FROMt1LEFTJOINt2ONt1.id1t2.id2ANDt2.name2cc;这样写系统会先按 name2 ‘cc’ 过滤 t2再拿过滤后的 t2 去和 t1 做外连接。t1 的所有记录都能返回匹配不上的那部分t2 的列照样是 NULL。这才是你最初想要的语义。记住这条分工ON 控制的是连接的规则WHERE 控制的是最终结果的筛选。这句话在整个迁移期间值得天天念。尤其要小心 Oracle 的()语法这条对迁移最关键。KES 兼容 Oracle 的()外连接写法这对迁移是好事但同一个坑也跟着来了。从 Oracle 过来的人手里带着()的老习惯最容易在这栽。规则是这样如果()写在 WHERE 里而同一个 WHERE 里的过滤条件没带()一样会触发外连接消除和前面 LEFT JOIN 那种情况一模一样。反过来如果过滤条件也带上()语义就等同于把条件放进了 ON外连接不会被消除。所以迁移老 Oracle 语句的时候凡是带()的都要一条条看清楚()有没有覆盖到过滤条件这直接决定了你的外连接活不活得下来。作用在左表的条件不用担心。如果过滤条件落在非空侧也就是左表比如WHERE t1.name1 a这属于正常的业务过滤意思是只对满足条件的 t1 记录做外连接不满足的直接丢掉它不改变连接的性质符合预期。要留意的是另一种写法如果你把左表条件放进 ON那 t1 的所有数据仍然会全部返回只是不满足条件的行不参与连接而已。这两者语义不同迁移时别混。五、落地排查清单把这个坑落到具体的迁移动作上给你三条能直接执行的。第一审执行计划。审涉及 OUTER JOIN 的慢 SQL、或者结果存疑的 SQL 时重点看计划。如果你定义的 Left Join在计划里显示成了普通的 Hash Join 或者 Nested Loop而不是 Left 语义的连接同时结果集行数比预期少那多半就是发生了非预期的外连接消除。第二校语义。心里立一条规矩针对右表这种可空侧的过滤除了查空的 IS NULL绝大多数都应该放进 ON。ON 管连接规则WHERE 管最终筛选这两句是迁移期的口头禅。第三一致性优先。外连接消除本身是个好优化性能是它的功劳平时求之不得。但在迁移场景里第一优先级不是性能是跟原系统逻辑对齐。任何优化器行为差异只要可能让业务数据和 Oracle 对不上都先按一致性处理性能的事往后放。六、为什么它排第一回到开头那个问题为什么我把外连接消除排在 Oracle 迁 KES 隐性陷阱的第一位。因为它是静默这一类坑的代表。它不报错不中断语法完全合法连优化器都没做错错的只是你以为的语义、和 SQL 真实语义之间那道看不见的缝。这种坑测试用例只要覆盖不全就一定漏往往要等上线之后业务方拿真实数据帮你发现代价最大。迁移这件事越往后走我越信一条让系统跑起来不难难的是让它跑出跟从前一模一样的结果。外连接消除是这条路上的第一课。这份隐性陷阱清单还没写完。空串和 NULL 的区别、隐式类型转换、日期格式、分页语法每一个都够单开一篇。后面一篇一篇慢慢聊。