在不使用函数结果缓存的情况下,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;
/
答案 0 :(得分:4)
添加内联视图和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 | |
---------------------------------
答案 1 :(得分:1)
您可以尝试deterministic
关键字将功能标记为纯。这是否真的能提高性能是另一个问题。
<强>更新强>
我不知道上面的例子是多么逼真,但理论上你总是可以尝试重新构造你的SQL,因此它知道重复的函数调用(实际重复的值)。有点像
select x,x from (
SELECT test_package.testSpeed('STANDARD', 'REGEXP_COUNT') x
FROM dual
)
答案 2 :(得分:0)
使用内嵌视图。
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的结果传递给第三个函数。