何时在Oracle的查询中评估用户定义的函数?

时间:2012-06-05 22:13:36

标签: oracle plsql oracle9i

我在SELECT语句中使用用户定义的函数时遇到了一些有趣的行为。

我有一些存储过程可以读取和清除单个表中的数据。这些存储过程由多个源使用。

在我的观察中,似乎用户定义的函数有时是任意评估的,并不总是在执行使用它的SELECT语句之后或期间立即进行评估。

例如,在存储过程中,我有一个select语句可能如下所示:

SELECT Something, MyFunction(Something) FROM Somewhere;

然后调用另一个存储过程,该过程从表中清除数据。清除的数据量由另一个表控制,该表存储最大ID读取。这样清除不应该删除任何尚未被执行的存储过程的另一个实例读取的数据。

在我的测试代码中,MyFunction只返回表Somewhere中的行数。因此,我认为它应该始终等于SELECT语句返回的行数。但是,如果我运行此存储过程的两个实例,我会得到如下结果:

第一个查询实例:

Something  MyFunction(Something)
---------  ---------------------
A          3
B          3
C          3

第二个查询实例:

Something  MyFunction(Something)
---------  ---------------------
A          0
B          0
C          0    

为什么第二个查询返回所有行,但在同一个表上运行的用户定义函数报告表中没有更多行?

无论如何,我可以确保第二个查询实例是一致的,因为用户定义的函数仍然看到父存储过程看到的相同数据吗?

1 个答案:

答案 0 :(得分:9)

通常,您看到的问题是由于Oracle的多版本读取一致性确保单个SQL语句始终能够看到数据的一致视图,因此相同的一致性并不意味着每个SQL语句由原始SQL语句调用的函数发出的函数将看到与原始语句相同的数据集。

实际上,这意味着像

这样的东西
SELECT something,
       COUNT(*) OVER ()
  FROM table_name
如果在函数中放置完全相同的逻辑,

将始终返回正确的答案(如果查询返回3行,则返回3)

CREATE OR REPLACE FUNCTION count_table_name
  RETURN NUMBER
AS
  l_cnt INTEGER;
BEGIN
  SELECT COUNT(*)
    INTO l_cnt
    FROM table_name;
  RETURN l_cnt;
END;

表示SQL语句

SELECT something,
       count_table_name
  FROM table_name

不一定会返回与表中的行数匹配的值(也不一定会为每一行返回相同的结果)。如果您对函数构建延迟,则可以看到实际操作,以便您可以在单独的会话中修改数据。例如

SQL> create table foo( col1 number );

Table created.

SQL> insert into foo select level from dual connect by level <= 3;

3 rows created.

创建一个每行添加10秒延迟的函数

SQL> ed
Wrote file afiedt.buf

  1  create or replace function fn_count_foo
  2    return number
  3  is
  4    l_cnt  integer;
  5  begin
  6    select count(*)
  7      into l_cnt
  8      from foo;
  9    dbms_lock.sleep(10);
 10    return l_cnt;
 11* end;
 12  /

Function created.

现在,如果在会话1中,我开始声明

select col1, fn_count_foo
  from foo;

然后切换到会话2,我插入一个新行

SQL> insert into foo values( 4 );

1 row created.

SQL> commit;

Commit complete.

你可以看到该函数在第二次执行期间看到新提交的行,尽管SQL语句本身只能看到3行

SQL> select col1, fn_count_foo
  2    from foo;

      COL1 FN_COUNT_FOO
---------- ------------
         1            3
         2            4
         3            4

通过在执行SQL语句之前让会话使用可序列化事务隔离级别,可以避免该问题。所以,例如,

在会话1中,将事务隔离级别设置为可序列化并启动查询

SQL> set transaction isolation level serializable;

Transaction set.

SQL> select col1, fn_count_foo
  2    from foo;

在第2节中,插入新行

SQL> insert into foo values( 5 );

1 row created.

SQL> commit;

Commit complete.

当会话1在40秒后返回时,一切都是一致的

SQL> select col1, fn_count_foo
  2    from foo;

      COL1 FN_COUNT_FOO
---------- ------------
         1            4
         2            4
         3            4
         4            4