2012-06-05 11 views
6

我在SELECT语句中使用用户定义的函数时遇到了一些有趣的行为。何时在Oracle中的查询中评估用户定义函数?

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

以我的观察,看来该用户定义的功能有时评估任意,并不总是立即后或SELECT语句的执行,它是在使用。

例如期间,在存储过程中,我有一个select语句可能看起来像这样:

SELECT Something, MyFunction(Something) FROM Somewhere; 

然后调用另一个存储过程,从表中清除数据。清除的数据量由另一个表进行管理,该表存储最大ID读取。这样一来,清除操作不应删除存储过程正在执行的另一个实例尚未读取的任何数据。

在我的测试代码中,MyFunction只是返回表中某处的行数。因此,我会想象它应该总是等于SELECT语句返回的行数。然而,在我运行此存储过程的两个实例的情况下,我得到的结果是这样的:

首先查询实例:

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

第二查询实例:

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

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

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

+0

我不确定明白:在2个电话之间做了y你可以清理桌子吗? – Sebas

+0

对此感到抱歉。为了说明,有一个存储过程(1)执行SELECT语句,并且(2)在SELECT语句之后调用清除过程。 – acee

+0

我明白了,所以函数返回正确的结果不是吗?选择不行,或者说不同的行不应该被返回。确认了吗? – Sebas

回答

9

一般来说,您所看到的问题是由于以下事实造成的:虽然Oracle的多版本读取一致性可确保单个SQL语句始终能够看到数据的一致视图,但这种一致性并不意味着每个SQL由原始SQL语句调用的函数发布的语句将看到与原始语句相同的一组数据。

在实际应用中,这意味着像

SELECT something, 
     COUNT(*) OVER() 
    FROM table_name 

总是返回正确的答案(3如果该查询返回3行),如果你把完全相同的逻辑功能

的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. 

,当第一节返回40秒后,一切都是一致的

SQL> select col1, fn_count_foo 
    2 from foo; 

     COL1 FN_COUNT_FOO 
---------- ------------ 
     1   4 
     2   4 
     3   4 
     4   4 
+0

啊,谢谢你的提示。我如何在存储过程的上下文中正确地设置它?该过程正在被另一个应用程序调用。 – acee

+0

@ashyu - 通常,应用程序可以设置自己的事务隔离级别,通常作为应用程序使用的任何“连接”类型对象的属性。您可以添加'set transaction isolation level'作为该过程的第一个语句,但是只有在对该过程的调用开始一个新事务时才会起作用 - 如果现有事务尝试调用,您将得到一个错误因为设置隔离级别的过程必须是事务的第一个操作。 –

+0

+1尼斯解释贾斯汀。我只是想补充一点,ORA-08177(不能序列化访问这个事务)错误,例如,如果会话2更新(和提交)一行,而会话1正在运行其长查询,并且稍后会话1尝试更新同一行。我想我的观点是大多数人习惯于处理读取提交隔离,因为它是默认的。 – tbone