【问题标题】:Are functions considered in Execution Plan?执行计划中是否考虑了功能?
【发布时间】:2021-06-14 17:10:49
【问题描述】:

当查询在 SELECT 或 WHERE 子句中有 PL/SQL 函数(用户定义函数)时,如何生成执行计划? 它是否也会计算这些函数的成本并将其显示在执行计划中,还是忽略这些函数?

提前感谢您的帮助。

【问题讨论】:

  • Oracle 有非常复杂的优化策略。基于成本的优化器允许开发人员收集 UDF 的统计信息,以便优化器可以更正确地考虑它们。
  • 感谢@GordonLinoff,但我如何才能看到优化器对 UDF 做了什么?例如,如果我在 UDF 中编写另一个 SELECT 查询,我无法在执行计划中看到该查询的计划。它只是为原始查询编写执行计划。我如何才能看到优化器对 UDF 的确切作用?
  • 有什么想法吗? @GordonLinoff

标签: sql oracle query-optimization sqlperformance


【解决方案1】:

用户生成的函数在 SELECT 或 WHERE 子句中使用时直接贡献很少或没有成本。如果我们希望优化器根据函数的成本做出决策,我们必须使用ASSOCIATE STATISTICS 命令手动设置成本。

示例架构

对于这个例子,创建以下中等大小的表和两个简单​​的函数 - 一个明显快,一个明显慢。

create table test1 as
select mod(level, 10) a, mod(level, 10) b
from dual
connect by level <= 100000;

begin
    dbms_stats.gather_table_stats(user, 'test1');
end;
/

create or replace function fast_function(p_number number) return number is
begin
    return p_number;
end;
/

create or replace function slow_function(p_number number) return number is
    v_count number;
begin
    select count(*)
    into v_count
    from all_tables;
    return v_count;
end;
/

SELECT 子句中的功能 - 无需费用

在 SELECT 子句中调用函数根本不会改变成本。以下三个查询选择文字、快速函数和慢速函数:

explain plan for select a from test1;
select * from table(dbms_xplan.display);

explain plan for select fast_function(a) from test1;
select * from table(dbms_xplan.display);

explain plan for select slow_function(a) from test1;
select * from table(dbms_xplan.display);

但是所有查询都生成相同的执行计划:

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |   100K|   292K|    47   (3)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| TEST1 |   100K|   292K|    47   (3)| 00:00:01 |
---------------------------------------------------------------------------

WHERE 子句中的功能 - 成本低

当调用 WHERE 子句中的函数而不是字面量时,成本从 48 略微增加到 70。但快速函数和慢速函数之间没有成本差异。

explain plan for select * from test1 where a = b;
select * from table(dbms_xplan.display);

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       | 10000 | 60000 |    48   (5)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST1 | 10000 | 60000 |    48   (5)| 00:00:01 |
---------------------------------------------------------------------------

explain plan for select * from test1 where fast_function(a) = b;
select * from table(dbms_xplan.display);

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       | 10000 | 60000 |    70  (35)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST1 | 10000 | 60000 |    70  (35)| 00:00:01 |
---------------------------------------------------------------------------

explain plan for select * from test1 where slow_function(a) = b;
select * from table(dbms_xplan.display);

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       | 10000 | 60000 |    70  (35)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST1 | 10000 | 60000 |    70  (35)| 00:00:01 |
---------------------------------------------------------------------------

关联统计

我们可以为函数的每次调用设置 cpu_cost、io_cost 和 network_cost。可能有一种方法可以使用跟踪找到这些特定成本,但成本是一个难以理解的内部幻数,优化器通常只需要一个数量级内的数字就可以做出正确的决策。我在慢函数中找到了查询的总成本,1000,并将其平均分为 cpu_cost 和 io_cost,如下所示:

associate statistics with functions slow_function default cost(500,500,0);

现在该计划的总成本从 70 急剧增加到 100,000,000:

explain plan for select * from test1 where b = slow_function(b);
select * from table(dbms_xplan.display);

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       | 10000 | 60000 |   100M  (1)| 01:05:07 |
|*  1 |  TABLE ACCESS FULL| TEST1 | 10000 | 60000 |   100M  (1)| 01:05:07 |
---------------------------------------------------------------------------

更重要的是,Oracle 可以使用此成本信息以正确的顺序运行函数。在下面的查询中,Oracle 先运行 fast 函数,几乎没有任何开销,然后在剩余的行上运行 slow 函数。

(函数执行的顺序有点难说。较低的总成本意味着函数是如何运行的。而FILTER中函数的顺序是另一个标志。在常规SQL中,AND的两侧谓词可以按任何顺序运行。在解释计划中,执行顺序似乎总是从左到右。)

explain plan for select * from test1 where a = fast_function(a) and b = slow_function(b);
select * from table(dbms_xplan.display);

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |  1000 |  6000 |    10M  (1)| 00:06:31 |
|*  1 |  TABLE ACCESS FULL| TEST1 |  1000 |  6000 |    10M  (1)| 00:06:31 |
---------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("A"="FAST_FUNCTION"("A") AND "B"="SLOW_FUNCTION"("B"))

选择性

尽管名称为“基于成本的优化器”,但我们可能应该更多地担心基数而不是成本。谓词返回的行数决定了大多数执行计划的选择。 Oracle 对用户定义的函数做了一些默认的猜测。例如,在下面的查询中,Oracle 假设函数只会满足 1% 的行 - 这就是为什么执行计划中的“行”说 1000 而不是 100000。

explain plan for select * from test1 where fast_function(a) = 1;
select * from table(dbms_xplan.display);

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |  1000 |  6000 |    70  (35)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST1 |  1000 |  6000 |    70  (35)| 00:00:01 |
---------------------------------------------------------------------------

如果我们知道该函数更具选择性,例如如果我们知道该函数更有可能只匹配所有行的 0.1%,我们还可以使用ASSOCIATE STATISTICS 设置默认选择性。下面的命令设置选择性,然后行数从 1000 下降到 100。

associate statistics with functions fast_function default selectivity 0.1;
explain plan for select * from test1 where fast_function(a) = 1;
select * from table(dbms_xplan.display);

---------------------------------------------------------------------------
| Id  | Operation         | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |       |   100 |   600 |    70  (35)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| TEST1 |   100 |   600 |    70  (35)| 00:00:01 |
---------------------------------------------------------------------------

在我们的简单计划中,基数无关紧要。但在实际查询中,可怕的基数估计会导致错误决策的连锁反应,从而导致查询缓慢。帮助优化器做出良好的基数估计通常是性能调优中最重要的部分。

其他类型的函数和统计

这个已经很长的答案仍然只是触及函数如何影响执行计划的表面。表函数,即返回数据行的函数,完全是另一个话题。而且我敢打赌,在优化器从错误中吸取教训之后,较新的 Oracle 版本中有动态重新优化功能将有助于改进第二次或第三次执行。

希望我没有阻止您使用自定义函数。绝大多数时候,Oracle 会毫不费力地做出正确的决定。如果没有,则有一些机制可以帮助纠正这些错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-06
    • 1970-01-01
    相关资源
    最近更新 更多