WHERE子句中的Oracle SQL函数比SELECT子句运行时间更长

时间:2016-02-15 20:51:24

标签: sql oracle oracle11g

真的这是两个相关的问题

问题:为什么查询在select子句中的运行速度比在where子句中的运行速度更快

问题:为什么在将where子句从内联视图移动到外部查询时,内联视图会花费更长的时间。

我不会转储整个查询,因为它有与我的工作相关的列和表,但基本上就是这样。如果你需要一个有效的例子,我会写一个类似于我正在做的SQLFiddle。

运行时间:117秒, 返回:93条记录

SELECT COL1, COL2, COL3
FROM my_table
WHERE [CONDITIONS...]
 and my_package.my_function(:bind_var, COL1) = 'Y';

如果我自己运行该函数,那将是bind var,它将是一个COL1值,它需要.06s。所以,

SELECT my_package.my_function(VAL1, VAL2) FROM DUAL;

所以我重写了这样的查询:

SELECT *
FROM (
   SELECT COL1, COL2, COL3
   FROM my_table
   WHERE [CONDITIONS...]
 ) temp_tbl
 WHERE my_package.my_function(:bind_var, COL1) = 'Y';

运行时间:116秒, 返回:93条记录

没有该函数的查询需要大约3秒才能运行,但是对于93条记录需要.06s的函数运行需要~116秒才有意义。

我试过看看如果我将函数移动到SELECT子句会发生什么。

SELECT *
FROM (
   SELECT COL1, COL2, COL3, my_package.my_function(:bind_var, COL1) as fn_indc
   FROM my_table
   WHERE [CONDITIONS...]
 ) temp_tbl
 WHERE fn_indc = 'Y';

当我运行内联视图查询时,运行需要约3秒。当我添加WHERE fn_indc = 'Y';时,运行需要大约116秒。为什么要将函数从WHERE移动到SELECT? CHAR的比较并不需要很长时间才能完成。另外,如果我创建了一个内联视图,它从函数中检索了值并在外部查询中执行了where where条件,那么会导致它运行的时间更长?

3 个答案:

答案 0 :(得分:1)

你没有给我们太多信息,所以我猜...

以下查询最有可能使用某些索引,因此与FTS(全表扫描)相比,它运行得更快:

SELECT *
FROM (
   SELECT COL1, COL2, COL3, my_package.my_function(:bind_var, COL1) as fn_indc
   FROM my_table
   WHERE [CONDITIONS...]
 ) temp_tbl
 WHERE fn_indc = 'Y';

所以它会通过相应的索引访问'my_table',然后它会将[my_package.my_function(:bind_var,COL1)]函数仅应用于属于结果集的行(即通过过滤获得的行)

如果您没有定义基于函数的索引,则oracle无法使用索引来执行以下查询:

SELECT *
FROM (
   SELECT COL1, COL2, COL3
   FROM my_table
   WHERE [CONDITIONS...]
 ) temp_tbl
 WHERE my_package.my_function(:bind_var, COL1) = 'Y';

所以它执行以下操作: 1. my_table的FTS(全表扫描) 2.对每行应用过滤器:my_package.my_function(:bind_var,COL1)='Y'

PS如果你要更改你的函数,那么它会返回[:bind_var]而不是期望它作为参数然后你可以构建基于函数的索引并按如下方式使用它:

SELECT COL1, COL2, COL3
FROM my_table
WHERE [CONDITIONS...]
 and my_package.my_function(COL1) = :bind_var;

答案 1 :(得分:1)

每种情况下执行的功能有多少次?

在没有查看查询计划的情况下,我会打赌当首先评估其他谓词时,查询会快速运行,在调用函数之前尽可能地减少结果集。当该函数仅被调用93次时(但是对于未被任何其他谓词消除的行需要许多额外的执行),查询会快速运行。另一方面,如果在查询计划中较早调用该函数,则会多次调用该函数 - 可能会对表中的每一行调用一次,并且查询返回的速度会慢得多。您可以通过查看查询计划或使用一些检测来精确测量在不同情况下调用函数的次数。

Oracle优化器可以根据统计信息以任何其认为合适的顺序自由地评估谓词。重写查询可能会导致优化器选择更好或更差的不同计划。但是明天,优化器可以完全自由地改变主意,并为您发布的任何变体使用较慢的计划。当然,墨菲是这片土地的法则,优化者很可能会等待最糟糕的时间来决定将你的查询计划转移给你,因为它会给你带来最大的痛苦和痛苦。

如果优化器认为快速计划和慢速计划的成本大致相同,那么这可能意味着它认为该功能要么比实际要便宜得多,要么比它实际上更具选择性。纠正错误信念的最好方法是使用函数associate statistics。这使您可以告诉优化器查询的价格以及查询的选择性。反过来,这可以让优化器做出更好的估算,并且无论您如何编写查询,它都可能会选择更有效的计划(并且使得计划在未来变得更糟的可能性更小)。

现在,您还可以通过编写查询来避免优化器通过使用提示或在内联视图中放置一些阻止谓词被推送来合并谓词的方式来作弊。一个老技巧是将rownum放入内联视图。当然,优化器的某些未来版本可能足够聪明,可以确定rownum在这里没有做任何事情,可以安全地删除。而且你需要为下一个出现的人留下一个很好的长评论,并想知道为什么你在查询中放置rownum时没有做任何事情。

SELECT *
FROM (
   SELECT COL1, COL2, COL3, rownum
   FROM my_table
   WHERE [CONDITIONS...]
 ) temp_tbl
 WHERE my_package.my_function(:bind_var, COL1) = 'Y';

答案 2 :(得分:0)

回答我的部分问题:

  

问题:为什么在移动位置时内联视图需要更长时间   从内联视图到外部查询的子句。

这是因为嵌套查询由sql优化器连接为外连接。所以我试图通过在sql优化器撤消函数之前运行其他条件来实现。如果我想强制sql优化器首先运行我的内联视图,我可以添加rownum >= 1

SELECT *
FROM (
   SELECT COL1, COL2, COL3
   FROM my_table
   WHERE [CONDITIONS...]
      and rownum >= 1
 ) temp_tbl
 WHERE my_package.my_function(:bind_var, COL1) = 'Y';

https://blogs.oracle.com/optimizer/entry/optimizer_transformations_subquery_unesting_part_1