8

此查询由 16 个相等的步骤组成。
每一步都在同一个数据集(单行)上进行相同的计算,
但最后一步花费了太多时间。

with t0 as (select 0 as k from dual)
,t1 as (select k from t0 where k >= (select avg(k) from t0))
,t2 as (select k from t1 where k >= (select avg(k) from t1))
,t3 as (select k from t2 where k >= (select avg(k) from t2))
,t4 as (select k from t3 where k >= (select avg(k) from t3))
,t5 as (select k from t4 where k >= (select avg(k) from t4))
,t6 as (select k from t5 where k >= (select avg(k) from t5))
,t7 as (select k from t6 where k >= (select avg(k) from t6))
,t8 as (select k from t7 where k >= (select avg(k) from t7))
,t9 as (select k from t8 where k >= (select avg(k) from t8))
,t10 as (select k from t9 where k >= (select avg(k) from t9))
,t11 as (select k from t10 where k >= (select avg(k) from t10))
,t12 as (select k from t11 where k >= (select avg(k) from t11)) -- 0.5 sec
,t13 as (select k from t12 where k >= (select avg(k) from t12)) -- 1.3 sec
,t14 as (select k from t13 where k >= (select avg(k) from t13)) -- 4.5 sec
,t15 as (select k from t14 where k >= (select avg(k) from t14)) -- 30 sec
,t16 as (select k from t15 where k >= (select avg(k) from t15)) -- 4 min
select k from t16

子查询 t10 立即完成,但整个查询 (t16) 需要 4 分钟才能完成。

Q1。
为什么相同数据的相同子查询的计算时间相差很大?

Q2。
它看起来像一个错误,因为它在 Oracle 9 上运行得非常快,而在 Oracle 11 上运行得非常慢。
事实上,每个带有长而复杂的 with 子句的 select 语句都会以相同的方式运行。
这是一个已知的错误吗?(我无法访问metalink)
推荐什么解决方法?

Q3。
我必须为 Oracle 11 编写代码,并且必须在单个 select 语句中完成所有计算。
我不能将我的长语句分成两个单独的语句来加快速度。
Oracle 中是否存在使整个查询(t16)在合理的时间内(例如,在一秒钟内)完成的提示(或者可能是一些技巧)?我试图找到这样一个,但无济于事。
顺便说一句,执行计划非常好,成本表现为步数的线性函数(不是指数)。

4

2 回答 2

8

Q1:似乎与计算时间无关,只是优化器算法中的错误使其在计算最佳执行计划时变得疯狂。

Q2:Oracle 11.X.0.X 中有许多与嵌套查询优化和查询分解相关的已知和已修复的错误。但是很难找到一个具体的问题。

Q3:有两个未记录的提示:但是当我尝试您的示例时materializeinline没有一个对我有用。服务器配置的某些更改或升级到 11.2.0.3 可能会增加嵌套with子句的限制:对我来说(在 11.2.0.3 Win7/x86 上)您的示例工作正常,但是将嵌套表的数量增加到 30 会挂起会话。

解决方法可能如下所示:

select k from (
select k, avg(k) over (partition by null) k_avg from ( --t16
  select k, avg(k) over (partition by null) k_avg from ( --t15
    select k, avg(k) over (partition by null) k_avg from ( --t14
      select k, avg(k) over (partition by null) k_avg from ( --t13
        select k, avg(k) over (partition by null) k_avg from ( --t12
          select k, avg(k) over (partition by null) k_avg from ( --t11
            select k, avg(k) over (partition by null) k_avg from ( --t10
              select k, avg(k) over (partition by null) k_avg from ( --t9
                select k, avg(k) over (partition by null) k_avg from ( --t8
                  select k, avg(k) over (partition by null) k_avg from ( --t7
                    select k, avg(k) over (partition by null) k_avg from ( --t6
                      select k, avg(k) over (partition by null) k_avg from ( --t5
                        select k, avg(k) over (partition by null) k_avg from ( --t4
                          select k, avg(k) over (partition by null) k_avg from ( --t3
                            select k, avg(k) over (partition by null) k_avg from ( --t2
                              select k, avg(k) over (partition by null) k_avg from ( -- t1
                                select k, avg(k) over (partition by null) k_avg from (select 0 as k from dual) t0
                              ) where k >= k_avg
                            ) where k >= k_avg
                          ) where k >= k_avg
                        ) where k >= k_avg
                      ) where k >= k_avg
                    ) where k >= k_avg
                  ) where k >= k_avg
                ) where k >= k_avg
              ) where k >= k_avg
            ) where k >= k_avg
          ) where k >= k_avg
        ) where k >= k_avg
      ) where k >= k_avg
    ) where k >= k_avg
  ) where k >= k_avg
) where k >= k_avg
)

至少它在 30 的嵌套级别上对我有用,并且使用WINDOW BUFFERandVIEW而不是LOAD TABLE AS SELECT, SORT AGGREGATEand生成完全不同的执行计划TABLE ACCESS FULL

更新

  1. 刚刚安装 11.2.0.4 (Win7/32bit) 并针对初始查询进行测试。优化器的行为没有任何改变。

  2. inline即使使用(未记录的)或RULE(不推荐的)提示,也不可能直接影响 CBO 行为。可能是一些大师知道一些变体,但它对我来说是最高机密(谷歌也是:-)。

  3. 如果将主选择语句分成几个部分并放入返回一组行的函数(返回 sys_refcursor 或强类型游标的函数)中,则可以在合理的时间内在一个选择语句中执行操作,但如果查询则不是选择在运行时构建。

  4. 使用 XML 的解决方法是可能的,但这个变体看起来像是从屁股洞里取出扁桃体(抱歉):

.

select
  extractvalue(column_value,'/t/somevalue') abc
from 
  table(xmlsequence((
    select t2 from (
      select
        t0,
        t1,
        (   
          select xmlagg(
                   xmlelement("t", 
                     xmlelement("k1",extractvalue(t1t.column_value,'/t/k1')), 
                     xmlelement("somevalue", systimestamp))
                  )
          from 
            table(xmlsequence(t0)) t0t, 
            table(xmlsequence(t1)) t1t  
          where 
            extractvalue(t1t.column_value,'/t/k1') >= (
              select avg(extractvalue(t1t.column_value, '/t/k1')) from table(xmlsequence(t1))
            )                                              
            and 
            extractvalue(t0t.column_value,'/t/k2') > 6
        ) t2
      from (
        select
          t0,
          (
            select xmlagg(
                     xmlelement("t", 
                       xmlelement("k1",extractvalue(column_value,'/t/k1')), 
                       xmlelement("somevalue", sysdate))
                    )
            from table(xmlsequence(t0))   
            where 
              extractvalue(column_value,'/t/k1') >= (
                select avg(extractvalue(column_value, '/t/k1')) from table(xmlsequence(t0))
              )
          ) t1
        from (
          select
            xmlagg(xmlelement("t", xmlelement("k1", level), xmlelement("k2", level + 3))) t0
          from dual connect by level < 5
        )
      )
    )
  )))

关于上面一段奇怪代码的另一件事是,这个变体仅适用于with数据集没有大量行的情况。

于 2013-11-06T05:20:34.333 回答
5

(这不是一个完整的答案。希望这里的信息可以帮助其他人产生更好的答案。)

Q1:优化器通过内联所有内容来重写查询。内部语句的大小随着每个新的公用表表达式而增加一倍,并且语句迅速变得庞大。例如,T15 生成 3,162,172 个字符的查询。

跟踪语句的代码:

sqlplus user/pass@orcl

alter session set events '10053 trace name context forever, level 1';

with t0 as (select 0 as k from dual)
,t1 as (select k from t0 where k >= (select avg(k) from t0))
,t2 as (select k from t1 where k >= (select avg(k) from t1))
select k from t2;

exit;


sqlplus user/pass@orcl

alter session set events '10053 trace name context forever, level 1';

with t0 as (select 0 as k from dual)
,t1 as (select k from t0 where k >= (select avg(k) from t0))
,t2 as (select k from t1 where k >= (select avg(k) from t1))
,t3 as (select k from t2 where k >= (select avg(k) from t2))
select k from t3;
exit;

如果比较这两个跟踪文件,会有很多不同之处,但大多数看起来都很细微。真正的区别仅在于字符串之后的一行:Stmt: ******* UNPARSED QUERY IS *******。如果您跟踪较大的查询,请小心打开跟踪文件。不是所有的编辑器都能处理这么大的行。T20文件是250MB!

格式化后来自第一个跟踪的 SQL:

SELECT "T1"."K" "K"
  FROM (SELECT 0 "K"
          FROM "SYS"."DUAL" "DUAL"
         WHERE 0 >= (SELECT AVG(0) "AVG(K)" FROM "SYS"."DUAL" "DUAL")) "T1"
 WHERE "T1"."K" >=
       (SELECT AVG("T1"."K") "AVG(K)"
          FROM (SELECT 0 "K"
                  FROM "SYS"."DUAL" "DUAL"
                 WHERE 0 >= (SELECT AVG(0) "AVG(K)" FROM "SYS"."DUAL" "DUAL")) "T1")

格式化后来自第二个跟踪的 SQL:

SELECT "T2"."K" "K"
  FROM (SELECT "T1"."K" "K"
          FROM (SELECT 0 "K"
                  FROM "SYS"."DUAL" "DUAL"
                 WHERE 0 >= (SELECT AVG(0) "AVG(K)" FROM "SYS"."DUAL" "DUAL")) "T1"
         WHERE "T1"."K" >=
               (SELECT AVG("T1"."K") "AVG(K)"
                  FROM (SELECT 0 "K"
                          FROM "SYS"."DUAL" "DUAL"
                         WHERE 0 >=
                               (SELECT AVG(0) "AVG(K)" FROM "SYS"."DUAL" "DUAL")) "T1")) "T2"
 WHERE "T2"."K" >=
       (SELECT AVG("T2"."K") "AVG(K)"
          FROM (SELECT "T1"."K" "K"
                  FROM (SELECT 0 "K"
                          FROM "SYS"."DUAL" "DUAL"
                         WHERE 0 >=
                               (SELECT AVG(0) "AVG(K)" FROM "SYS"."DUAL" "DUAL")) "T1"
                 WHERE "T1"."K" >=
                       (SELECT AVG("T1"."K") "AVG(K)"
                          FROM (SELECT 0 "K"
                                  FROM "SYS"."DUAL" "DUAL"
                                 WHERE 0 >= (SELECT AVG(0) "AVG(K)"
                                               FROM "SYS"."DUAL" "DUAL")) "T1")) "T2")

Q2:我不会说每个“复杂”的公用表表达式的行为方式都相同。我见过更大的 CTE。似乎只有极端的嵌套才是问题所在。我在 Oracle Support 上找不到任何明显的错误。

ThinkJet 的代码看起来不错。嵌套内联视图比嵌套公用表表达式更常见。

Q3:可能有防止这种行为的提示,但我不确定它是什么。希望通过显示查询的转换版本,其他人可以猜出如何修复它。

于 2013-11-06T06:55:46.173 回答