Sql Oracle性能:执行多个相同函数调用的查询

Sql Oracle性能:执行多个相同函数调用的查询,sql,oracle,performance,function,Sql,Oracle,Performance,Function,在同一查询(事务?)中调用函数时,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_t

在同一查询(事务?)中调用函数时,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

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

使用的功能:

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;
/

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

更新:

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

select x,x from (
    SELECT test_package.testSpeed('STANDARD', 'REGEXP_COUNT') x
      FROM dual
)
使用内嵌视图

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的结果传递给第三个函数

添加内联视图和
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     |      |
---------------------------------

您仍然可以使用结果缓存和“releases_on”关键字来处理表更改-为什么不使用WITH子句调用函数一次,然后在查询中引用该结果?在12c中,您可以在SQL语句中声明函数,这与PL/SQL非常不同(从事务角度看)使用WITH子句没有帮助,因为它仍然执行函数两次。我看不出你是如何依赖关键字更改的,在11gR2中,依赖项是自动识别的,而且表多次获得数据更改,从而使缓存失效。我专门寻找同一查询中的缓存。12c WITH FUNCTION子句很有趣,我可能可以在这里或那里应用它,但它并不能解决更大的问题,尽管带有嵌套函数的函数非常复杂,并且利用了我正在使用的应用程序框架。问题真的是因为为一个特定id多次调用函数,或者当您尝试选择多行时(从my_表中选择my_函数(id),其中id介于1和100000之间)?您描述的情况将导致对函数的少量调用,而我描述的第二种情况更糟。但有一个技巧可能适用于第二种情况不,这不是一个确定性函数。每次对相同的输入返回相同的结果。@OldProgrammer如果函数不是deretministic的,那么您就有麻烦了。因为Oracle不保证对order语句求值的顺序。所以你永远不知道你得到了什么。从SQL调用的函数调用SQL是反模式的。这通常会导致恶劣的数据传输。内部SQL存在于不同的事务上下文中,可能会看到幻象数据。该函数不能是确定性的,因为确定性的定义对于相同的参数会得到相同的结果。但是,在这种情况下,参数可以相同,但基础数据可以更改,从而导致不同的返回值。而且,即使添加确定性,也没有什么区别。它仍然执行了两次。哇,这真是出乎意料。举个例子,它是有效的!这对我的“大局”问题没有帮助,但在某些情况下我肯定可以使用它。但有一件事我根本不明白为什么添加ROWNUM会改变解释计划/使Oracle只执行一次函数。对此有更深刻的解释吗?非常感谢您的帮助,谢谢!ROWNUM返回行的返回顺序,因此它必须是执行的最后一步。任何类型的转换都会更改顺序,因此Oracle无法修改具有ROWNUM的查询块。(理论上,优化器可以识别何时没有使用ROWNUM,但这是一个罕见的问题,并且只会破坏许多使用此技巧的代码。)太棒了,我从来没有想过这一点。我将把这标记为最佳答案,因为我认为在同一事务中没有“缓存”的解决方案。函数结果cac
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     |      |
---------------------------------