Oracle 12.2中OR子句条件顺序影响左连接查询结果的问题
Oracle 12.2中OR子句顺序导致查询结果不一致的问题分析与解决
最近在Oracle 12.2数据库里碰到了一个挺诡异的问题:两个结构完全一致,仅OR条件顺序不同的查询,在同版本另一数据库的相似数据集上运行正常,但当前库中结果却截然不同。先给大家还原下场景:
场景前提
tbl_bestand表关联的两个实体(tbl_kb_artikel和tbl_artikel)始终有且仅有一个非空,本次测试场景中,关联的Article非空、KbArticle为空。
两个对比查询
第一个查询(能返回预期结果):
SELECT bestand0_.* FROM tbl_bestand bestand0_ LEFT OUTER JOIN tbl_kb_artikel customeror1_ ON bestand0_.id_kb_artikel=customeror1_.id_kb_artikel LEFT OUTER JOIN tbl_artikel article2_ ON bestand0_.id_artikel =article2_.id_artikel WHERE (customeror1_.id_kb_artikel=3017874 OR article2_.id_artikel =3017874) AND bestand0_.cod_lbr =12 AND NVL(bestand0_.flg_sperre,0) = 0;
第二个查询(无结果返回,仅调换了OR条件的顺序):
SELECT bestand0_.* FROM tbl_bestand bestand0_ LEFT OUTER JOIN tbl_kb_artikel customeror1_ ON bestand0_.id_kb_artikel=customeror1_.id_kb_artikel LEFT OUTER JOIN tbl_artikel article2_ ON bestand0_.id_artikel =article2_.id_artikel WHERE (article2_.id_artikel =3017874 OR customeror1_.id_kb_artikel=3017874) AND bestand0_.cod_lbr =12 AND NVL(bestand0_.flg_sperre,0) = 0;
排查过程中的测试细节
- 去掉最后两个过滤条件(
AND bestand0_.cod_lbr =12和AND NVL(bestand0_.flg_sperre,0) = 0)后,两个查询均能返回预期结果。 - 尝试过添加
AND 对应关联ID IS NULL的判断(比如针对空的id_kb_artikel加非空排除),但没能解决问题。 - 最后尝试**禁用Adaptive Statistics Optimizer(自适应统计优化器)**后,两个查询都能正常返回结果了。
问题原因分析
这大概率是Oracle 12.2的自适应统计优化器在生成执行计划时的异常逻辑导致的。当OR条件顺序变化时,优化器对查询的执行路径判断出现偏差——尤其是在左连接后其中一个关联表为空的场景下,优化器可能错误地过滤掉了符合条件的数据。
自适应优化器本是根据运行时统计信息动态调整执行计划,但在某些特定的数据分布或关联场景下,可能出现逻辑判断错误,不同的OR顺序触发了错误的执行计划,进而返回空结果。
解决方案
针对这个问题,有几种可行的解决方式:
- 临时禁用自适应统计优化器(已验证有效)
可以通过会话级或系统级参数关闭:- 会话级(仅影响当前会话,推荐):
ALTER SESSION SET optimizer_adaptive_statistics = FALSE; - 系统级(需谨慎,会影响所有会话):
ALTER SYSTEM SET optimizer_adaptive_statistics = FALSE;
- 会话级(仅影响当前会话,推荐):
- 强制指定执行计划
通过添加hint来固定正确的执行计划,比如参考第一个查询的执行计划,添加类似/*+ USE_NL(bestand0_ article2_) */的提示(具体需根据实际执行计划调整)。 - 重构查询逻辑
将OR条件拆分为两个查询用UNION ALL合并,避免优化器的判断偏差。由于业务上两个关联实体有且仅有一个非空,UNION ALL不会产生重复数据:SELECT bestand0_.* FROM tbl_bestand bestand0_ LEFT OUTER JOIN tbl_kb_artikel customeror1_ ON bestand0_.id_kb_artikel=customeror1_.id_kb_artikel LEFT OUTER JOIN tbl_artikel article2_ ON bestand0_.id_artikel =article2_.id_artikel WHERE customeror1_.id_kb_artikel=3017874 AND bestand0_.cod_lbr =12 AND NVL(bestand0_.flg_sperre,0) = 0 UNION ALL SELECT bestand0_.* FROM tbl_bestand bestand0_ LEFT OUTER JOIN tbl_kb_artikel customeror1_ ON bestand0_.id_kb_artikel=customeror1_.id_kb_artikel LEFT OUTER JOIN tbl_artikel article2_ ON bestand0_.id_artikel =article2_.id_artikel WHERE article2_.id_artikel =3017874 AND bestand0_.cod_lbr =12 AND NVL(bestand0_.flg_sperre,0) = 0; - 更新表统计信息
重新收集相关表的统计信息,让优化器获得更准确的数据分布,可能解决自适应优化的判断错误:EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME => '你的用户名', TABNAME => 'tbl_bestand', CASCADE => TRUE); EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME => '你的用户名', TABNAME => 'tbl_kb_artikel', CASCADE => TRUE); EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME => '你的用户名', TABNAME => 'tbl_artikel', CASCADE => TRUE);
内容的提问来源于stack exchange,提问作者peach
相关产品推荐
相关产品推荐

