Oracle的pipelined函式提升資料輸出效能
從Oracle 8開始,我們就可以從一個collection型別的資料集合中查詢出資料,這個集合稱之為“虛擬表“。它的方法是“SELECT FROM TABLE(CAST(plsql_function AS collection_type))”,據說該方法在處理大資料量時會有記憶體方面的限制。到了Oracle 9i之後,一個稱為pipelined表函式的技術被推出來。他和普通的表函式很類似,但還有有一些顯著的差別。
第一,pipelined函式處理的資料,是以管道的方式,或者說是流的方式從預先準備的小陣列中展現給使用者,而普通表函式將資料全部準備好再展現給使用者。
第二,pipelined函式可以併發,這意味著PLSQL可以同一時間在多個程序上執行。
第三,pipelined函式可以很容易將儲存過程轉換成用bulk操作的行,有利於實現的複雜轉換邏輯的SQL。
瞭解pipelined函式的最佳方法是看一個簡單的例子。對於任何一個pipelined函式,都必須有兩點要求。
1、一個定義在模式中或者包中collection型別;
2、一個單獨的PL/SQL函式或一個包中的函式,函式的返回型別後面必須加pipelined關鍵字;
在下面的例子中,我們將建立一個簡單的pipelined函式,輸出若干行記錄。首先需要一個collection型別,用於格式化輸出。
- CREATEORREPLACE TYPE number_ntt ASTABLEOF NUMBER;
CREATE OR REPLACE TYPE number_ntt AS TABLE OF NUMBER;
Oracle會使用這個型別快取少量的記錄作為pipelined函式呼叫時的輸出。我們建立一個簡單的pipelined函式。
- CREATEORREPLACEFUNCTION row_generator(rows_in IN PLS_INTEGER)
- RETURN number_ntt PIPELINED
- IS
- BEGIN
- FOR i IN 1 .. rows_in LOOP
- PIPE ROW(i);
- END LOOP;
- RETURN;
- END;
CREATE OR REPLACE FUNCTION row_generator(rows_in IN PLS_INTEGER) RETURN number_ntt PIPELINED IS BEGIN FOR i IN 1 .. rows_in LOOP PIPE ROW(i); END LOOP; RETURN; END;
在這個SQL中:
在函式定義部分的關鍵字pipelined是pipelined函式定義的關鍵,返回的型別必須是事先定義的collection型別,如這裡是number_tt。
在函式主體部分的”PIPE ROW”是將一個單行記錄寫入到collection流中。記錄中所有欄位的型別必須和collection型別中所有欄位匹配。
在函式主體部分的“return“的值是一個空值,而不是有任何符合collection類的值。
這些就是pipelined函式定義時需要嚴格遵守的規則。
現在已經建立好一個pipelined函式,我們可以測試一下。
- SQL> select * fromTABLE( row_generator(10) );
- COLUMN_VALUE
- ------------
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 10 rows selected
SQL> select * from TABLE( row_generator(10) );
COLUMN_VALUE
------------
1
2
3
4
5
6
7
8
9
10
10 rows selected
將pipelined函式row_generator放到一個“TABLE”操作符中,虛擬成一個數據源,類似表或檢視。這裡虛擬表只有一個欄位,名稱“COLUMN_VALUE“是其預設值。更復雜的輸出則需要將collection定義得更復雜些,使用到object或者record。
我們通過一個例子比較一下pipelined函式或普通的表函式在返回collection時有何差異。
第一步,建立普通的表函式,返回colletion型別。
- CREATEORREPLACEFUNCTION table_function RETURN number_ntt AS
- nt number_ntt := number_ntt();
- BEGIN
- FOR i IN 1 .. 500000 LOOP
- if (mod(i, 10000) = 0) then
- nt.EXTEND;
- nt(nt.LAST) := i;
- end if;
- END LOOP;
- RETURN nt; --<-- return whole collection
- END table_function;
CREATE OR REPLACE FUNCTION table_function RETURN number_ntt AS
nt number_ntt := number_ntt();
BEGIN
FOR i IN 1 .. 500000 LOOP
if (mod(i, 10000) = 0) then
nt.EXTEND;
nt(nt.LAST) := i;
end if;
END LOOP;
RETURN nt; --<-- return whole collection
END table_function;
第二步,建立pipelined函式,返回的也是collection型別
- CREATEORREPLACEFUNCTION pipelined_function RETURN number_ntt
- PIPELINED AS
- BEGIN
- FOR i IN 1 .. 500000 LOOP
- if (mod(i, 10000) = 0) then
- PIPE ROW(i); --<-- send row to consumer
- end if;
- END LOOP;
- RETURN;
- END pipelined_function;
CREATE OR REPLACE FUNCTION pipelined_function RETURN number_ntt
PIPELINED AS
BEGIN
FOR i IN 1 .. 500000 LOOP
if (mod(i, 10000) = 0) then
PIPE ROW(i); --<-- send row to consumer
end if;
END LOOP;
RETURN;
END pipelined_function;
函式的功能都是將能和1000整除的數字輸出出來。
再建立一個輸出時間到毫秒的函式,用於測試兩個函式的輸出特點。
- CREATEFUNCTION get_time RETURNTIMESTAMPIS
- BEGIN
- RETURN LOCALTIMESTAMP;
- END get_time;
- /
CREATE FUNCTION get_time RETURN TIMESTAMP IS
BEGIN
RETURN LOCALTIMESTAMP;
END get_time;
/
第三步,測試兩個函式
測試普通函式如下:
- ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'HH24:MI:SS.FF3';
- SELECT get_time() AS ts FROM DUAL;
- SELECT column_value, get_time() AS ts FROMTABLE(table_function);
- SQL>
- TS
- --------------------------------------------------------------------------------
- 15:27:26.031
- COLUMN_VALUE TS
- ------------ --------------------------------------------------------------------------------
- 100000 15:27:26.218
- 200000 15:27:26.218
- 300000 15:27:26.218
- 400000 15:27:26.218
- 500000 15:27:26.218
- SQL>
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'HH24:MI:SS.FF3';
SELECT get_time() AS ts FROM DUAL;
SELECT column_value, get_time() AS ts FROM TABLE(table_function);
SQL>
TS
--------------------------------------------------------------------------------
15:27:26.031
COLUMN_VALUE TS
------------ --------------------------------------------------------------------------------
100000 15:27:26.218
200000 15:27:26.218
300000 15:27:26.218
400000 15:27:26.218
500000 15:27:26.218
SQL>
結果顯示,所有記錄都是同一時間輸出。
測試pipelined函式如下:
Sql程式碼- SELECT get_time() AS ts FROM DUAL;
- SELECT column_value, get_time() AS ts FROMTABLE(pipelined_function);
- TS
- --------------------------------------------------------------------------------
- 15:27:26.265
- COLUMN_VALUE TS
- ------------ --------------------------------------------------------------------------------
- 100000 15:27:26.312
- 200000 15:27:26.343
- 300000 15:27:26.390
- 400000 15:27:26.421
- 500000 15:27:26.453
SELECT get_time() AS ts FROM DUAL;
SELECT column_value, get_time() AS ts FROM TABLE(pipelined_function);
TS
--------------------------------------------------------------------------------
15:27:26.265
COLUMN_VALUE TS
------------ --------------------------------------------------------------------------------
100000 15:27:26.312
200000 15:27:26.343
300000 15:27:26.390
400000 15:27:26.421
500000 15:27:26.453
結果顯示,所有記錄都是逐次輸出。
這點對於使用者的UI太重要了。試想,如果執行一個查詢,過了10秒鐘才顯示出所有的結果好,還是還是每秒都顯示一些記錄,知道10秒鐘顯示完畢好?
如果這個輸出的結果集再放到到百萬記錄,兩個函式對PGA記憶體的消耗又完全不一樣,這點更重要。