1. 程式人生 > >Oracle的pipelined函式提升資料輸出效能

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型別,用於格式化輸出。

Sql程式碼 複製程式碼 收藏程式碼
  1. CREATEORREPLACE TYPE number_ntt ASTABLEOF NUMBER;  
CREATE OR REPLACE TYPE number_ntt AS TABLE OF NUMBER;
 


Oracle會使用這個型別快取少量的記錄作為pipelined函式呼叫時的輸出。我們建立一個簡單的pipelined函式。

Sql程式碼 複製程式碼 收藏程式碼
  1. CREATEORREPLACEFUNCTION row_generator(rows_in IN PLS_INTEGER)  
  2.   RETURN number_ntt  PIPELINED  
  3. IS
  4. BEGIN
  5.   FOR i IN 1 .. rows_in LOOP  
  6.     PIPE ROW(i);  
  7.   END LOOP;  
  8.   RETURN;  
  9. 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程式碼 複製程式碼 收藏程式碼
  1. SQL> select * fromTABLE( row_generator(10) );  
  2. COLUMN_VALUE  
  3. ------------
  4.            1  
  5.            2  
  6.            3  
  7.            4  
  8.            5  
  9.            6  
  10.            7  
  11.            8  
  12.            9  
  13.           10  
  14. 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型別。

Sql程式碼 複製程式碼 收藏程式碼
  1. CREATEORREPLACEFUNCTION table_function RETURN number_ntt AS
  2.   nt number_ntt := number_ntt();  
  3. BEGIN
  4.   FOR i IN 1 .. 500000 LOOP  
  5.     if (mod(i, 10000) = 0) then
  6.       nt.EXTEND;  
  7.       nt(nt.LAST) := i;  
  8.     end if;  
  9.   END LOOP;  
  10.   RETURN nt; --<-- return whole collection
  11. 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型別

Sql程式碼 複製程式碼 收藏程式碼
  1. CREATEORREPLACEFUNCTION pipelined_function RETURN number_ntt  
  2.   PIPELINED AS
  3. BEGIN
  4.   FOR i IN 1 .. 500000 LOOP  
  5.     if (mod(i, 10000) = 0) then
  6.       PIPE ROW(i); --<-- send row to consumer
  7.     end if;  
  8.   END LOOP;  
  9.   RETURN;  
  10. 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整除的數字輸出出來。
再建立一個輸出時間到毫秒的函式,用於測試兩個函式的輸出特點。

Sql程式碼 複製程式碼 收藏程式碼
  1. CREATEFUNCTION get_time RETURNTIMESTAMPIS
  2. BEGIN
  3.    RETURN LOCALTIMESTAMP;  
  4. END get_time;  
  5. /  
CREATE FUNCTION get_time RETURN TIMESTAMP IS
BEGIN
   RETURN LOCALTIMESTAMP;
END get_time;
/
 


第三步,測試兩個函式


測試普通函式如下:

Sql程式碼 複製程式碼 收藏程式碼
  1. ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'HH24:MI:SS.FF3';  
  2. SELECT get_time() AS ts FROM DUAL;  
  3. SELECT column_value, get_time() AS ts FROMTABLE(table_function);  
  4. SQL>  
  5. TS  
  6. --------------------------------------------------------------------------------
  7. 15:27:26.031  
  8. COLUMN_VALUE TS  
  9. ------------ --------------------------------------------------------------------------------
  10.       100000 15:27:26.218  
  11.       200000 15:27:26.218  
  12.       300000 15:27:26.218  
  13.       400000 15:27:26.218  
  14.       500000 15:27:26.218  
  15. 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程式碼 複製程式碼 收藏程式碼
  1. SELECT get_time() AS ts FROM DUAL;  
  2. SELECT column_value, get_time() AS ts FROMTABLE(pipelined_function);  
  3. TS  
  4. --------------------------------------------------------------------------------
  5. 15:27:26.265  
  6. COLUMN_VALUE TS  
  7. ------------ --------------------------------------------------------------------------------
  8.       100000 15:27:26.312  
  9.       200000 15:27:26.343  
  10.       300000 15:27:26.390  
  11.       400000 15:27:26.421  
  12.       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記憶體的消耗又完全不一樣,這點更重要。