ace*_*cee 6 oracle plsql oracle9i
我在SELECT语句中使用用户定义的函数时遇到了一些有趣的行为.
我有几个存储过程从单个表中读取和清除数据.这些存储过程由多个源使用.
在我的观察中,似乎用户定义的函数有时是任意评估的,并不总是在执行它所使用的SELECT语句之后或期间立即进行评估.
例如,在存储过程中,我有一个select语句可能看起来像这样:
SELECT Something, MyFunction(Something) FROM Somewhere;
Run Code Online (Sandbox Code Playgroud)
然后调用另一个存储过程,该过程从表中清除数据.清除的数据量由另一个表控制,该表存储最大ID读取.这样清除不应该删除任何尚未被执行的存储过程的另一个实例读取的数据.
在我的测试代码中,MyFunction只返回表Somewhere中的行数.因此,我认为它应该始终等于SELECT语句返回的行数.但是,在我运行此存储过程的两个实例的情况下,我得到如下结果:
第一个查询实例:
Something MyFunction(Something)
--------- ---------------------
A 3
B 3
C 3
Run Code Online (Sandbox Code Playgroud)
第二个查询实例:
Something MyFunction(Something)
--------- ---------------------
A 0
B 0
C 0
Run Code Online (Sandbox Code Playgroud)
为什么第二个查询返回所有行,但在同一个表上运行的用户定义函数报告表中没有更多行?
无论如何,我可以确保第二个查询实例是一致的,因为用户定义的函数仍然看到父存储过程看到的相同数据?
通常,您看到的问题是由于Oracle的多版本读取一致性确保单个SQL语句始终能够看到数据的一致视图,因此相同的一致性并不意味着每个SQL语句都由原始SQL语句调用的函数将看到原始语句所执行的相同数据集.
实际上,这意味着类似的东西
SELECT something,
COUNT(*) OVER ()
FROM table_name
Run Code Online (Sandbox Code Playgroud)
如果在函数中放置完全相同的逻辑,将始终返回正确的答案(如果查询返回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;
Run Code Online (Sandbox Code Playgroud)
那个SQL语句
SELECT something,
count_table_name
FROM table_name
Run Code Online (Sandbox Code Playgroud)
不一定会返回与表中的行数匹配的值(也不一定会为每一行返回相同的结果).如果您对函数构建延迟,则可以看到实际操作,以便您可以在单独的会话中修改数据.例如
SQL> create table foo( col1 number );
Table created.
SQL> insert into foo select level from dual connect by level <= 3;
3 rows created.
Run Code Online (Sandbox Code Playgroud)
创建一个每行添加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.
Run Code Online (Sandbox Code Playgroud)
现在,如果在会话1中,我开始声明
select col1, fn_count_foo
from foo;
Run Code Online (Sandbox Code Playgroud)
然后切换到会话2,我插入一个新行
SQL> insert into foo values( 4 );
1 row created.
SQL> commit;
Commit complete.
Run Code Online (Sandbox Code Playgroud)
您可以看到该函数在第二次执行期间看到新提交的行,尽管SQL语句本身只能看到3行
SQL> select col1, fn_count_foo
2 from foo;
COL1 FN_COUNT_FOO
---------- ------------
1 3
2 4
3 4
Run Code Online (Sandbox Code Playgroud)
您可以通过在执行SQL语句之前使会话使用可序列化事务隔离级别来避免该问题.所以,例如,
在会话1中,将事务隔离级别设置为可序列化并启动查询
SQL> set transaction isolation level serializable;
Transaction set.
SQL> select col1, fn_count_foo
2 from foo;
Run Code Online (Sandbox Code Playgroud)
在会话2中,插入一个新行
SQL> insert into foo values( 5 );
1 row created.
SQL> commit;
Commit complete.
Run Code Online (Sandbox Code Playgroud)
当会话1在40秒后返回时,一切都是一致的
SQL> select col1, fn_count_foo
2 from foo;
COL1 FN_COUNT_FOO
---------- ------------
1 4
2 4
3 4
4 4
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5063 次 |
| 最近记录: |