确定性函数何时使用先前的计算值?
Posted
技术标签:
【中文标题】确定性函数何时使用先前的计算值?【英文标题】:When does a deterministic function use the previous calculated value? 【发布时间】:2014-01-28 08:51:34 【问题描述】:考虑一个确定性函数,例如:
CREATE OR REPLACE FUNCTION SCHEMA.GET_NAME(ss_id nvarchar2
) RETURN nvarchar2 DETERMINISTIC IS
tmpVar nvarchar2(500);
BEGIN
select name into tmpvar from logistics.organization_items
where id = ss_id ;
return tmpvar ;
END ss_name;
使用 Toad,我调用了 SCHEMA.GET_NAME(1)
,它返回 A
。然后我将表中的值从 A
更改为 B
并回忆 SCHEMA.GET_NAME(1)
返回的 B
。
这是一个很好的结果。但是我怕值没有按照this page in the documentation更新,上面写着:
当 Oracle 数据库在其中一种情况下遇到确定性函数时,它会尽可能尝试使用先前计算的结果,而不是重新执行该函数。如果您随后更改了函数的语义,则必须手动重建所有依赖的基于函数的索引和物化视图。
在什么情况下GET_NAME(1)
的值会返回一个旧的缓存值(A
而不是B
)?
【问题讨论】:
【参考方案1】:如果您从表中选择,那么您的函数的结果是非确定性的。 deterministic system 会总是在相同的初始条件下产生相同的输出。
可以更改表中的信息,因此从表中选择的函数不是确定性的。引用PL/SQL Language Reference:
不要指定此子句来定义使用包变量或以任何可能影响函数返回结果的方式访问数据库的函数。如果数据库选择不重新执行函数,则不会捕获这样做的结果。
换句话说,Oracle 不保证函数的结果是准确的(他们只是可能)。如果您的桌子是静态的,并且不太可能改变,那么应该没问题,但这不是我想要依赖的东西。要回答您的问题,请不要假设 Oracle 将在同一事务/会话中返回缓存值以外的任何内容。
如果您需要加快速度,有两种方法。首先,检查您在ID
上是否有索引!
只需加入此表即可。如果你的函数只有这个,那么这个函数就不需要存在了。
使用scalar sub-query caching(不一定可以,但值得一试)。
select ( select get_name(:id) from dual )
from your_table
Oracle 将创建函数结果的内存哈希,就像结果缓存一样。如果你多次执行同一个函数,那么 Oracle 将命中缓存而不是函数。
【讨论】:
感谢您的完整回答。我也喜欢现在这个函数的缓存多久刷新一次?在执行查询的范围内?按时间间隔?根据内存需求释放缓存? 或者缓存仅在会话期间有效。那么对于新的会话,它将是更新吗? @mehrandvd - 我不知道这个功能的来龙去脉,但我感觉它就像大多数其他优化一样:它发生在优化器喜欢它的时候。换句话说,没有办法保证它被缓存/更新(有一些概率,主要是缓存,但不能保证)。这类似于在没有ORDER BY
的情况下运行查询 - 您会得到优化器决定给您的顺序,而不是您想要/期望的顺序。
我从来没有真正读过任何关于刷新用于此的缓存的频率@mehrandvd。结果缓存已满时会刷新,但我找不到太多关于确定性函数使用什么缓存方法的信息。【参考方案2】:
Ben 的回答很好地总结了这一点,我想补充一点,您在函数中使用 DETERMINISTIC
关键字的方式不正确 - 请注意您正在从表中读取值,然后返回相同的值给用户。
当您在固定输入上评估表达式时,应使用确定性函数,例如,当您需要返回子字符串或输入字符串的大写/小写时。以编程方式,您知道对于相同的输入,小写函数将始终返回相同的值,因此您希望缓存结果(使用确定性关键字)。
当你从表中读取一个值时,Oracle 无法知道列中的值没有改变,因此它更喜欢重新执行函数而不依赖于缓存的结果(这是有道理的)
【讨论】:
【参考方案3】:你能在你的函数中添加一个时间戳参数吗?然后将 sysdate 从您调用的任何位置传递给该函数。
这样,您可以有效地缓存结果,并且避免在函数通常在给定事务中返回相同值时反复运行该函数。
【讨论】:
【参考方案4】:Erez 的评论正是我一直在寻找的答案。 在执行查询或 plsql-unit 之前,您可以使用此解决方案强制在重置函数的 ret 值(例如更改包 var)后再次执行该函数。 我将其用于: 选择 ... 来自 big_table_vw;
在哪里
创建视图 big_table_vw 作为 选择...(分析函数) 来自大表 其中 last_mutated >= get_date();
在我的例子中,big_table_vw 包含阻止 Oracle 将谓词推送到视图中的窗口函数。
【讨论】:
更正:我只是尝试在没有时间戳的情况下多次调用该函数,结果是每个 sql 语句都会调用一次该函数。因此,不需要时间戳。【参考方案5】:这是对一个长期回答的问题的后期跟进,但我只想补充一点,Oracle 确实为具有可变依赖关系的函数提供了缓存机制。 RESULT_CACHE
是 DETERMINISTIC
的替代品,它允许 Oracle 在任何时候修改引用的对象时放弃缓存的函数结果。
通过这种方式,人们可以对很少更新的对象缓存昂贵的计算,并确信缓存的结果不会返回不正确的结果。
这是一个使用神话怪物的例子:
CREATE TABLE MONSTER (
MONSTER_NAME VARCHAR2(100) NOT NULL PRIMARY KEY
);
INSERT INTO MONSTER VALUES ('Chthulu');
INSERT INTO MONSTER VALUES ('Grendel');
INSERT INTO MONSTER VALUES ('Scylla');
INSERT INTO MONSTER VALUES ('Nue');
COMMIT;
CREATE OR REPLACE PACKAGE MONSTER_PKG
IS
FUNCTION IS_THIS_A_MONSTER(P_MONSTER_NAME IN VARCHAR2)
RETURN BOOLEAN RESULT_CACHE;
END MONSTER_PKG;
/
CREATE OR REPLACE PACKAGE BODY MONSTER_PKG
IS
FUNCTION IS_THIS_A_MONSTER(P_MONSTER_NAME IN VARCHAR2)
RETURN BOOLEAN
RESULT_CACHE RELIES_ON (MONSTER)
IS
V_MONSTER_COUNT NUMBER(1, 0) := 0;
BEGIN
SELECT COUNT(*)
INTO V_MONSTER_COUNT
FROM MONSTER
WHERE MONSTER_NAME = P_MONSTER_NAME;
RETURN (V_MONSTER_COUNT > 0);
END;
END MONSTER_PKG;
/
当出现以下情况时,任何现有缓存都会失效,然后可以重建新的缓存。
BEGIN
DBMS_OUTPUT.PUT_LINE('Is Kraken initially a monster?');
IF MONSTER_PKG.IS_THIS_A_MONSTER('Kraken')
THEN
DBMS_OUTPUT.PUT_LINE('Kraken is initially a monster');
ELSE
DBMS_OUTPUT.PUT_LINE('Kraken is not initially a monster');
END IF;
INSERT INTO MONSTER VALUES ('Kraken');
COMMIT;
DBMS_OUTPUT.PUT_LINE('Is Kraken a monster after update?');
IF MONSTER_PKG.IS_THIS_A_MONSTER('Kraken')
THEN
DBMS_OUTPUT.PUT_LINE('Kraken is now a monster');
ELSE
DBMS_OUTPUT.PUT_LINE('Kraken is not now a monster');
END IF;
END;
/
Kraken 最初是怪物吗? 海妖最初不是怪物 海妖更新后是怪物吗? Kraken 现在是一个怪物
【讨论】:
以上是关于确定性函数何时使用先前的计算值?的主要内容,如果未能解决你的问题,请参考以下文章