Oracle 12c 子查询分解内联视图现在有不好的计划?

Oracle 12c Subquery Factoring Inline View now has bad plan?

11 月 2 日更新

经过一些额外的故障排除后,我的团队能够将此 Oracle 错误直接与查询停止工作前一天晚上在 12c 数据库上进行的参数更改联系起来。在与该数据库关联的应用程序遇到一些性能问题后,我的团队让我们的 DBA 将 OPTIMIZER_FEATURES_ENABLE 参数从 12.1.02 更改为 11.2.0.4。这解决了问题应用程序的性能问题,但导致了我上面描述的错误。为了验证,我已经能够通过更改此参数在单独的环境中复制相同的问题。我的 DBA 已向 Oracle 提交了一张票,要求对其进行检查。

作为变通方法,我能够对我的查询进行轻微更改以检索预期结果。具体来说,我将 Subquery1Subquery2 组合在一起,并将 Subquery1 中的一些谓词从 WHERE 子句移动到 JOIN(它们更适合属于)。此更改编辑了我的执行计划(它比之前列出的效率略低)但足以解决原始问题。


原版Post

首先,对于这个问题中的任何含糊之处,我深表歉意,但我正在处理一个机密的财务系统,所以我不得不隐藏某些实施细节。

背景

我有一个很久以前投入生产的 Oracle 查询,最近在从 11g 升级到 12c 后巧合地停止生成预期结果。据我(和我的生产支持团队)所知,此查询在此之前的一年多时间里一直运行良好。

详情

查询过于复杂且效率不高,但这在很大程度上是因为我正在处理非规范化 tables(历史上以大型机为模型)和来自上游系统的不良数据输入。为了处理复杂的业务情况,我利用了多个级别的子查询分解(WITH 语句),然后我的最终语句将两个内联视图连接在一起。没有所有复杂谓词的查询的基本结构如下:

我有 3 table 个 Table1Table2Table3Table1 是由来自 Table2.

的记录组成的处理 table
--This grabs a subset from Table1
WITH Subquery1 as (
   SELECT FROM Table1),

--This eliminates certain records from the first subset based on sister records 
--from the original source table 
Subquery2 as (
   SELECT FROM Subquery1
   WHERE NOT EXISTS FROM (SELECT from Table2)),

--This ties the records from Subquery2 to Table3
Subquery3 as (
   SELECT FROM Table3
   JOIN (SELECT Max(Date) FROM Table3)
   JOIN Subquery2)

--This final query evaluates subquery3 in two different ways and 
--only takes those records which fit the criteria items from both sets
SELECT FROM 
(SELECT FROM Subquery3)             -- Call this Inline View A
JOIN (SELECT FROM Subquery3)        -- Call this Inline View B

最终查询 非常基础:

   SELECT A.Group_No, B.Sub_Group, B.Key, B.Lob               
   FROM   (SELECT Group_No, Lob, COUNT(Sub_Group) 
           FROM   Subquery3 
           GROUP BY Group_No, Lob
           HAVING COUNT(Sub_Group) = 1) A 
   JOIN (SELECT Group_No, Sub_Group, Key, Lob
         FROM   Subquery3 
         WHERE  Sub_Group LIKE '0000%') B 
   ON A.Group_No = B.Group_No
   AND A.Lob = B.Lob

问题

如果我编辑最终查询以删除第二个内联视图并评估 A 内联视图的输出,我会得到 0 返回行 。我手动评估了每个单独子查询的记录,可以确认这是预期结果。

同样,如果我编辑最终查询以仅生成 'B' 内联视图的输出,我会得到 6 返回行 。同样,我手动评估了数据,这完全符合预期。

现在,当将这两个子集(内联视图 A 和内联视图 B)连接在一起时,我预计最终查询结果将是 0 行(因为一个完整的内部连接集和空集不产生匹配)。但是,当我 运行 如上所述使用内部连接进行整个查询时, 我得到了 1158 行 !

我已经审阅了执行计划,但没有任何问题:

问题

很明显,我做了一些事情来混淆 Oracle 优化器,更新后的查询计划正在拉回一个与我提交的查询截然不同的查询。我最好的猜测是,由于所有这些临时视图都在同一个查询中浮动,我已经混淆了 Oracle 在它所依赖的之前评估一些集合。

直到今天,我都无法找到有关 WITH 语句的官方 Oracle 文档,因此我对子查询的计算顺序一直没有完全的信心。我确实注意到在搜索 SO(现在找不到它)时有人提到分解子查询不能引用另一个分解查询。我以前从来不知道这是真的,但上面奇怪的输出让我想知道我之前是否只是幸运地使用了这个查询?

任何人都可以解释我所看到的行为吗?我是否试图用这个查询计划做一些明显不正确的事情?或者,是否有可能在 11g 和 12c 之间发生某些变化,这可以解释为什么此查询的行为可能发生变化?

这听起来像是 Oracle 中的一个 "wrong results" 错误。这些错误通常非常特定于您使用的版本和功能。您 post 编辑的查询或执行计划没有明显错误。

您有两种处理方式:

  1. 尝试找到准确的错误。你用常见的 table 表达式所做的看起来不错。在极少数情况下,您的查询在技术上是无效的,您在一个版本中获得 "lucky" 并且它有效,而当您升级时它失败了。但是当这种情况发生时,新版本通常会抛出一个错误,而不是 return 错误的结果。可能是您正在使用的一些非常奇怪的特定功能组合导致了问题。要找到真正的问题,您需要大量简化查询,直到您可以做出尽可能小的更改并看到问题出现和消失。您还需要删除所有对象并仅使用 DUAL。此过程可能需要数小时。最后,当您只剩下几行代码时,可以 post 在这里,查看 Oracle 支持,或者创建一个服务请求。
  2. 避免错误。即使您执行了上述步骤,也可能无法修复。有时最好的解决方法是做一些不同的事情。查明每个问题的根源固然很好,但您并不总是有时间。相反,尝试以语法上不同但逻辑上等效的方式重写查询。删除一些或所有常见的 table 表达式,甚至可能重复一些 SQL。但一定要留下评论警告未来的程序员为什么你以一种奇怪的方式做事。