2012-06-05 10 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