一般来说,您看到的问题是由于 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.
当 Session 1 在 40 秒后返回时,一切都是一致的
SQL> select col1, fn_count_foo
2 from foo;
COL1 FN_COUNT_FOO
---------- ------------
1 4
2 4
3 4
4 4