【问题标题】:Oracle performance: query executing multiple identical function callsOracle 性能:查询执行多个相同的函数调用
【发布时间】:2015-07-27 17:19:51
【问题描述】:

Oracle 是否可以在不使用函数结果缓存的情况下,在同一个查询(事务?)中调用函数的结果时重用它的结果?

我正在使用的应用程序严重依赖 Oracle 函数。许多查询最终会多次执行完全相同的函数。

一个典型的例子是:

SELECT my_package.my_function(my_id),
       my_package.my_function(my_id) / 24,
       my_package.function_also_calling_my_function(my_id)
  FROM my_table
 WHERE my_table.id = my_id;

我注意到 Oracle 总是执行这些函数中的每一个,而没有意识到同一函数在一秒钟前在同一个查询中被调用。函数中的某些元素可能会被缓存,从而导致返回速度稍快。这与我的问题无关,因为我想避免整个第二次或第三次执行。

假设这些函数相当消耗资源,并且这些函数可能会调用更多函数,它们的结果基于相当大且更新频繁的表(一百万条记录,每小时更新 1000 次)。因此无法使用 Oracle 的函数结果缓存。

即使数据经常变化,我希望这些函数在从同一个查询中调用时的结果是相同的。

Oracle 是否可以重用这些函数的结果以及如何重用?我正在使用 Oracle11g 和 Oracle12c。

下面是一个例子(只是一个随机的无意义函数来说明问题):

-- Takes 200 ms
SELECT test_package.testSpeed('STANDARD', 'REGEXP_COUNT')
  FROM dual;

-- Takes 400ms
SELECT test_package.testSpeed('STANDARD', 'REGEXP_COUNT')
     , test_package.testSpeed('STANDARD', 'REGEXP_COUNT')
  FROM dual;

用到的函数:

CREATE OR REPLACE PACKAGE test_package IS

FUNCTION testSpeed (p_package_name VARCHAR2, p_object_name VARCHAR2)
RETURN NUMBER;
END;
/

CREATE OR REPLACE PACKAGE BODY test_package IS

FUNCTION testSpeed (p_package_name VARCHAR2, p_object_name VARCHAR2)
RETURN NUMBER
IS

    ln_total NUMBER;

BEGIN

    SELECT SUM(position) INTO ln_total 
      FROM all_arguments 
     WHERE package_name = 'STANDARD' 
       AND object_name = 'REGEXP_COUNT';

    RETURN ln_total;

END testSpeed;

END;
/

【问题讨论】:

  • 您仍然可以使用结果缓存和“relies_on”关键字来处理表更改 - oracle.com/technetwork/issue-archive/2010/10-sep/… 为什么不使用 WITH 子句调用该函数一次,然后在查询中引用该结果?
  • 在 12c 中,您可以在 SQL 语句中声明函数,这与 PL/SQL 非常不同(从事务的角度来看)
  • 使用 WITH 子句并没有帮助,因为不幸的是它仍然执行了两次函数。我看不到 RELIES_ON 关键字如何改变任何东西,在 11gR2 中,依赖关系被自动识别,并且表多次获取数据更改,从而使缓存无效。我正在专门寻找在同一个查询中的缓存。 12c WITH FUNCTION 子句很有趣,我可以在这里和那里应用它,但它并不能解决更大的问题,尽管当具有嵌套函数的函数非常复杂并利用我正在使用的应用程序框架时。
  • 问题真的来自为 1 个特定 id 调用函数几次,或者当您尝试选择多行时(从 my_table 中选择 my_function(id),其中 id 介于 1 和 100000 之间)?您描述的情况将导致对该函数的少量调用,而我描述的第二种情况要糟糕得多。但是有一个技巧可能适用于第二种情况

标签: sql oracle performance function


【解决方案1】:

添加内联视图和ROWNUM 以防止Oracle 将查询重写为单个查询块并多次执行函数。


示例函数和问题演示

create or replace function wait_1_second return number is
begin
    execute immediate 'begin dbms_lock.sleep(1); end;';
    -- ...
    -- Do something here to make caching impossible.
    -- ...
    return 1;
end;
/

--1 second
select wait_1_second() from dual;

--2 seconds
select wait_1_second(), wait_1_second() from dual;

--3 seconds
select wait_1_second(), wait_1_second() , wait_1_second() from dual;

无效的简单查询更改

这两种方法仍然需要 2 秒,而不是 1 秒。

select x, x
from
(
    select wait_1_second() x from dual
);

with execute_function as (select wait_1_second() x from dual)
select x, x from execute_function;

强制 Oracle 按特定顺序执行

很难告诉 Oracle“自行执行此代码,不要对其进行任何谓词推送、合并或其他转换”。这些优化中的每一个都有提示,但它们很难使用。有几种方法可以禁用这些转换,添加一个额外的 ROWNUM 通常是最简单的。

--Only takes 1 second
select x, x
from
(
    select wait_1_second() x, rownum
    from dual
);

很难确切地看到函数在哪里被评估。但是这些解释计划显示了ROWNUM 如何导致内联视图单独运行。

explain plan for select x, x from (select wait_1_second() x from dual);
select * from table(dbms_xplan.display(format=>'basic'));

Plan hash value: 1388734953

---------------------------------
| Id  | Operation        | Name |
---------------------------------
|   0 | SELECT STATEMENT |      |
|   1 |  FAST DUAL       |      |
---------------------------------

explain plan for select x, x from (select wait_1_second() x, rownum from dual);
select * from table(dbms_xplan.display(format=>'basic'));

Plan hash value: 1143117158

---------------------------------
| Id  | Operation        | Name |
---------------------------------
|   0 | SELECT STATEMENT |      |
|   1 |  VIEW            |      |
|   2 |   COUNT          |      |
|   3 |    FAST DUAL     |      |
---------------------------------

【讨论】:

  • 哇,这太出乎意料了。例如,它有效!它对我的“大局”问题没有帮助,但在某些情况下我绝对可以使用它。不过有一件事,我完全不明白为什么添加 ROWNUM 会改变解释计划/使 Oracle 只执行一次函数。对此有更深层次的解释吗?非常感谢您的帮助,谢谢!
  • ROWNUM 返回行的返回顺序,因此它必须是执行的最后一步。任何类型的转换都会改变顺序,因此 Oracle 无法修改带有 ROWNUM 的查询块。 (理论上优化器可以识别出何时没有使用 ROWNUM,但这是一个罕见的问题,并且只会破坏大量使用此技巧的代码。)
  • 太棒了,我从来没有想过这个。我将把它标记为最佳答案,因为我认为在同一个事务中没有“缓存”的解决方案。函数结果缓存可能是最接近此的东西。谢谢!
  • 他们应该给你这个答案的奖牌,我有同样的问题,但我仍然无法相信优化器无法识别未使用的 rownum。你知道这种行为是否记录在某处吗?
【解决方案2】:

使用内嵌视图。

with get_functions as(
SELECT my_package.my_function(my_id) as func_val,
   my_package.function_also_calling_my_function(my_id) func_val_2
  FROM my_table
 WHERE my_table.id = my_id
)
select func_val,
   func_val / 24 as func_val_adj,
   func_val_2
from get_functions;

如果要消除对第 3 项的调用,请将 func_val 的结果传递给第三个函数。

【讨论】:

  • 我不明白,它不会改变任何事情......它仍然最终独立执行所有功能。不过感谢您的帮助!编辑:我明白你现在的意思,但这不是我所追求的。作为我的例子,我的错是对问题的非常简化的解释。稍后我会看看是否可以为我的问题添加一个更现实的示例/解释。
【解决方案3】:

您可以尝试使用deterministic 关键字将函数标记为纯函数。不过,这是否真的能提高性能是另一个问题。

更新:

我不知道你上面的例子有多现实,但理论上你总是可以尝试重新构建你的 SQL,以便它知道重复的函数调用(实际上是重复的值)。有点像

select x,x from (
    SELECT test_package.testSpeed('STANDARD', 'REGEXP_COUNT') x
      FROM dual
)

【讨论】:

  • 不,这不是确定性函数。它不会每次都为相同的输入返回相同的结果。
  • @OldProgrammer 是功能不是确定性的,那么你就有麻烦了。因为 Oracle 不保证语句将被评估的顺序。所以你永远不知道你得到了什么。从 SQL 调用的 FUNCTION 调用 SQL 是反模式。这通常会导致讨厌的数据currptions。内部 SQL 存在于不同的事务上下文中,并且可能会看到幻像数据。
  • 函数不能是确定性的,因为确定性的定义对于相同的参数得到相同的结果。但是,在这种情况下,参数可以相同,但基础数据可以更改,从而导致返回值不同。此外,即使添加确定性,也没有什么不同。它仍然执行两次。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-22
  • 1970-01-01
  • 2021-03-09
  • 1970-01-01
  • 2020-12-01
相关资源
最近更新 更多