确定性函数何时使用先前计算的值?

时间:2014-01-28 08:51:35

标签: oracle plsql oracle11g

考虑确定性函数,如:

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没有更新的价值,{{3}}说:

  

当Oracle数据库在其中一个上下文中遇到确定性函数时,它会尝试在可能的情况下使用先前计算的结果,而不是重新执行该函数。如果随后更改了函数的语义,则必须手动重建所有依赖于函数的索引和物化视图。

GET_NAME(1)的值在什么情况下会返回旧的缓存值(A而不是B)?

5 个答案:

答案 0 :(得分:6)

如果您从表中选择,那么您的函数结果确定性。给定相同的初始条件,deterministic system始终生成相同输出的那个。

可以更改表中的信息,因此从表中选择的函数不是确定性的。引用PL/SQL Language Reference

  

不要指定此子句来定义使用包变量的函数,或以任何可能影响函数返回结果的方式访问数据库的函数。如果数据库选择不重新执行该功能,则不会捕获这样做的结果。

换句话说,Oracle并不保证函数的结果是准确的(它们可能就是这样)。如果你的表是静态的,并且不太可能改变,那么它应该没问题,但这不是我想要依赖的东西。要回答您的问题,请不要假设Oracle将在同一事务/会话中返回除缓存值以外的任何内容。

如果你需要加快速度,有两种方法。首先,检查您是否有ID的索引!

  1. 只需加入此表即可。如果你的功能只是这个,那么就不需要存在这个功能。

  2. 使用scalar sub-query caching(不一定可能,但值得一试)。

    select ( select get_name(:id) from dual )
       from your_table
    

    Oracle将创建函数结果的内存中散列,就像结果缓存一样。如果您多次执行相同的功能,那么Oracle将点击缓存而不是功能。

答案 1 :(得分:1)

Ben的答案很好地总结了,我想补充说你在函数中使用DETERMINISTIC关键字的方式不对 - 保持你从表中读取值然后返回与用户相同。

在您通过固定输入计算表达式的情况下,应使用确定性函数,例如,当您需要返回子字符串或输入字符串的大写/小写时。以编程方式,您知道对于相同的输入,小写函数将始终返回相同的值,因此您希望缓存结果(使用确定性关键字)。

当你从表中读取一个值时,Oracle无法知道列中的值没有改变,因此它更喜欢重新执行该函数而不依赖于缓存的结果(这是有意义的)

答案 2 :(得分:0)

您可以在函数中添加时间戳参数吗?然后将sysdate传递给你调用它的函数。

这样,您可以有效地缓存结果,并且当它通常在给定事务中返回相同的值时,可以避免反复运行该函数。

答案 3 :(得分:0)

Erez的评论是我一直在寻找的答案。 在执行查询或plsql-unit之前,您可以使用此解决方案强制在重新设置函数的ret值后再次执行该函数(例如,更改包var)。 我用它来: 选择 ... 来自big_table_vw;

,其中

创建视图big_table_vw 如 选择...(分析功能) 来自big_table 其中last_mutated> = get_date();

在我的例子中,big_table_vw包含阻止Oracle将谓词推入视图的窗口函数。

答案 4 :(得分:0)

这是对一个长期回答的问题的后续跟进,但我只想补充一点,Oracle确实为具有可变依赖性的函数提供了缓存机制。 RESULT_CACHEDETERMINISTIC的替代方法,允许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是怪物吗? 海妖现在是一个怪物