【问题标题】:Deterministic function call from Cursor Oracle not working来自 Cursor Oracle 的确定性函数调用不起作用
【发布时间】:2021-04-14 07:54:42
【问题描述】:

我创建了一个简单的确定性函数,我在游标中使用选择查询来调用它 如下图

CREATE TABLE TEMP
(dt DATE); 

INSERT INTO   TEMP
SELECT SYSDATE FROM DUAL CONNECT BY LEVEL<=3;   
INSERT INTO   TEMP
SELECT SYSDATE+1 FROM DUAL CONNECT BY LEVEL<=3;     

COMMIT;

--2 distinct values
SELECT DISTINCT dt from TEMP;

包功能

CREATE OR REPLACE PACKAGE dummy_fun
AUTHID CURRENT_USER
IS
    FUNCTION get_data(
                  p_date  IN DATE)
    RETURN DATE
    DETERMINISTIC;

END dummy_fun;
/


CREATE OR REPLACE PACKAGE BODY dummy_fun
IS
     FUNCTION get_data(
                  p_date IN DATE)
    RETURN DATE
    DETERMINISTIC
    IS
        
    BEGIN
         DBMS_OUTPUT.PUT_LINE('get_data with input (p_date=>'||p_date||' called)');
        
        RETURN p_date+1;

    END get_data;  
  
END dummy_fun;
/

FUNCTION CALL - 期望 get_data 在 2 个不同的日期只被调用两次,而如果我只调用这个 SQL,它只运行两次

DECLARE

CURSOR get_date
IS 
SELECT dummy_fun.get_data (
               dt)  from 
TEMP;

rec get_date%ROWTYPE;
v_date date;
BEGIN

OPEN get_date;
LOOP
  FETCH get_date INTO rec;
  EXIT WHEN get_date%NOTFOUND;
  NULL;
END LOOP;
CLOSE get_date;

END;
/

输出


get_data with input (p_date=>14-APR-21 called)
get_data with input (p_date=>14-APR-21 called)
get_data with input (p_date=>14-APR-21 called)
get_data with input (p_date=>14-APR-21 called)
get_data with input (p_date=>24-APR-21 called)
get_data with input (p_date=>24-APR-21 called)
get_data with input (p_date=>24-APR-21 called)
get_data with input (p_date=>24-APR-21 called)

**有以下更改,它在光标中工作**

CHANGE 1 - IF THE FUNCTION IS CALLED IN THE WHERE CLAUSE 
CURSOR get_date
IS 
SELECT 1  from 
TEMP
WHERE trunc(sysdate+1)= trunc(ae9_common_code.dummy_fun.get_data (
               dt))

CHANGE 2 - Kind of Scalar subquery 
CURSOR get_date
IS 
SELECT * FROM (
SELECT ae9_common_code.dummy_fun.get_data (
               dt) from 
TEMP
WHERE 1=1)

CHANGE 3 - BULK COLLECT 

SELECT ae9_common_code.dummy_fun.get_data (
               dt) BULK COLLECT INTO v_dates from 
TEMP
WHERE 1=1;

##OUTPUT FOR ALL THE ABOVE CHANGES ARE##
get_data with input (p_date=>14-APR-21 called)
get_data with input (p_date=>24-APR-21 called)

【问题讨论】:

  • The documentation 说:如果一个带有 DETERMINISTIC 子句的函数违反了这些语义规则中的任何一个,那么它的调用结果、它的值以及对它的调用者的影响都是 未定义并且:出现DETERMINISTIC选项时,编译器可能使用该标记来提高函数的执行性能。
  • @astentx:我上面违反了什么语义?
  • 这个:DETERMINISTIC 函数可能没有副作用。
  • 有趣的是:如果我在没有游标的情况下执行相同的select 语句,每个值只会调用一次。如果我将result_cache 添加到定义中,它也会在游标中执行一次。看起来文档中有很多 可能 是造成这种情况的原因:有时优化器决定重用计算,有时不依赖于某些内部算法。

标签: oracle function oracle12c deterministic


【解决方案1】:

简而言之,确定性函数缓存取决于 fetch size(arraysize) - 结果仅在一次 fetch 调用中缓存,ssc(标量子查询缓存)没有这个限制。

请阅读我关于确定性函数的系列文章:

【讨论】:

  • 知道了,但即使在设置数组大小之后,光标中的结果也不如预期。你能指导我如何使它工作吗?
  • 我的意思是...光标查询只有 1 次调用,理想情况下它应该缓存所有内容
  • @StayCurious 您在循环中一次获取一行。 FETCH get_date INTO rec; 仅获取 1 行。因此,您正在执行 9 次 fetch 调用来获取所有行(8 次 - 获取所有行,1 次额外 fetch 用于 no_data_found)。只需为您的语句检查 v$sql.fetches 中的值
【解决方案2】:

当你有:

open cur;
loop
  fetch cur into ...
end loop;

数据库一次只获取一行。正如@SayanMalakshinov 所指出的那样,数据库不会跨提取缓存deterministic 结果。

使用 bulk collect 并限制一次获取 1、2 或更多行可能有助于更清楚地说明这一点:

create or replace procedure fetch_rows ( num_rows int ) as
  cursor get_date is 
    select dummy_fun.get_data ( dt )  
    from   temp;
  
  type rec_tab is table of get_date%rowtype
    index by pls_integer;
  rws rec_tab;

begin

  open get_date;
  loop
    fetch get_date 
    bulk collect into rws
    limit num_rows;
    exit when get_date%notfound;
  end loop;
  close get_date;

end;
/

exec fetch_rows ( 1 );

get_data with input (p_date=>14-APR-2021 10:32:36 called)
get_data with input (p_date=>14-APR-2021 10:32:36 called)
get_data with input (p_date=>14-APR-2021 10:32:36 called)
get_data with input (p_date=>15-APR-2021 10:32:36 called)
get_data with input (p_date=>15-APR-2021 10:32:36 called)
get_data with input (p_date=>15-APR-2021 10:32:36 called)

exec fetch_rows ( 2 );

get_data with input (p_date=>14-APR-2021 10:32:36 called)
get_data with input (p_date=>14-APR-2021 10:32:36 called)
get_data with input (p_date=>15-APR-2021 10:32:36 called)
get_data with input (p_date=>15-APR-2021 10:32:36 called)

exec fetch_rows ( 3 );

get_data with input (p_date=>14-APR-2021 10:32:36 called)
get_data with input (p_date=>15-APR-2021 10:32:36 called)

限制为 1,每一行都是一个新的提取,所以没有缓存。将其设置为 2 并且(可能)每隔一行被缓存。最多三个,每次提取最多缓存 2 行,等等。

由于一大堆其他原因,单行提取速度也很慢,所以实际上您应该考虑使用bulk collect,无论如何限制至少为 100。

请注意,PL/SQL 引擎优化 cursor-for 循环以一次获取 100 行,因此您还可以通过编写这样的循环来获得缓存效果:

begin

  for rws in ( 
    select dummy_fun.get_data ( dt )  
    from   temp
  ) 
  loop
    null;
  end loop;

end;
/

get_data with input (p_date=>14-APR-2021 10:32:36 called)
get_data with input (p_date=>15-APR-2021 10:32:36 called)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-12-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多