批量SQL操作之批量獲取
0 前言
PL/SQL中的批量操作,能一次操作和處理許多行,而不是一次一行。批量獲取操作是指一次呼叫把資料庫一組行讀入PL/SQL結構中,而不是每讀入一行就要發起一次對資料庫的呼叫。
1 設定數值案例
1 -----建立“測試表”
2 CREATE TABLE hardware
3 (
4 aisle NUMBER(5),
5 item NUMBER(12),
6 descr CHAR(50)
7 );
8
9 ------ 使用dual生成任意行技巧
10 INSERT /* +Append */ INTO hardware
11 SELECT TRUNC(ROWNUM/1000)+1 aisle,
12 ROWNUM item,
13 'Description' || ROWNUM descr
14 FROM
15 (SELECT 1 FROM dual CONNECT BY LEVEL <= 1000),
16 (SELECT 1 FROM dual CONNECT BY LEVEL <= 1000);
2 在PL/SQL中執行批量操作
2.1 早先PL/SQL提取資料行的方式
在引入批量操作之前(oracle8.1前),PL/SQL提取資料行的方式
(1)隱式遊標
一個標準的SQL查詢(select-into)用於從表中提取一行資料,或提取該行中的若干列儲存到目標變數。如果沒有提取行或提取資料超過一行,就會引發異常。下面是一個例子:
SQL> -- 隱式遊標
SQL> DECLARE
2 l_descr hardware.descr%TYPE;
3 BEGIN
4 SELECT descr
5 INTO l_descr
6 FROM hardware
7 WHERE aisle = 1
8 AND item=1;
9 dbms_output.put_line(l_descr);
10 END;
11 /
PL/SQL procedure successfully completed
(2)顯示獲取呼叫
遊標在PL/SQL
SQL> -- 顯示獲取呼叫
SQL> DECLARE
2 CURSOR c_tool_list IS
3 SELECT descr
4 FROM hardware
5 WHERE aisle = 1
6 AND item BETWEEN 1 AND 500;
7
8 l_descr hardware.descr%TYPE;
9 BEGIN
10 OPEN c_tool_list;
11 LOOP
12 FETCH c_tool_list INTO l_descr;
13 dbms_output.put_line(l_descr); --Ora-20000: buffer overflow,則PL/SQL Developer 緩衝區大小
14 EXIT WHEN c_tool_list%NOTFOUND;
15 END LOOP;
16 CLOSE c_tool_list;
17 dbms_output.put_line(l_descr);
18 END;
19 /
PL/SQL procedure successfully completed
(3)隱式獲取呼叫
For迴圈執行遊標管理,遊標在for迴圈中進行編碼。
SQL> -- 隱式獲取呼叫
SQL> BEGIN
2 FOR i IN (
3 SELECT descr
4 FROM hardware
5 WHERE aisle = 1
6 AND item BETWEEN 1 AND 500
7 )
8 LOOP
9 dbms_output.put_line(i.descr); --Ora-20000: buffer overflow,則PL/SQL Developer 緩衝區大小
10 END LOOP;
11 END;
12 /
PL/SQL procedure successfully completed
2.2 三種常用的批量收集及呼叫形式
(1)隱式遊標批量模式
只需在一個標準的SQL查詢(select-into)中新增bulk collect關鍵字,實現把多行資料提取到一個集合型別中。下面是一個例子:
SQL> -- 隱式遊標批量模式
SQL> DECLARE
2 TYPE t_descr_list IS TABLE OF hardware.descr%TYPE;
3 l_descr_list t_descr_list;
4 BEGIN
5 SELECT descr
6 BULK COLLECT
7 INTO l_descr_list
8 FROM hardware
9 WHERE aisle=1
10 AND item BETWEEN 1 AND 100;
11 dbms_output.put_line(l_descr_list(100));
12 FOR i IN 1..100 LOOP
13 dbms_output.put_line(l_descr_list(i)); --Ora-20000: buffer overflow,則PL/SQL Developer 緩衝區大小
14 END LOOP;
15 END;
16 /
PL/SQL procedure successfully completed
(2)顯示批量獲取呼叫模式
定義一個集合型別儲存結果,並在Fetch命令中新增bulk collect子句,就可以在一次呼叫中把遊標結果集中的所有行提取到集合型別的變數中去。下面是一個例子:
SQL> -- 顯示批量獲取呼叫模式
SQL> DECLARE
2 CURSOR c_tool_list IS
3 SELECT descr, item
4 FROM hardware
5 WHERE aisle=1
6 AND item BETWEEN 1 AND 500;
7
8 TYPE t_descr_list IS TABLE OF c_tool_list%ROWTYPE;
9 l_descr_list t_descr_list;
10 BEGIN
11 OPEN c_tool_list;
12 FETCH c_tool_list BULK COLLECT INTO l_descr_list;
13 CLOSE c_tool_list;
14
15 for r in l_descr_list.first .. l_descr_list.last loop
16 dbms_output.put_line(l_descr_list(r).descr);
17 dbms_output.put_line(l_descr_list(r).item);
18 end loop;
19 END;
20 /
PL/SQL procedure successfully completed
(3)隱式批量獲取呼叫模式
Oracle10g之後,對for迴圈的“自動批量收集”增強。按照定義,對遊標使用for迴圈是要從for迴圈中獲取所有行,除非在迴圈內部有明確的exit命令,那麼PL/SQL編譯器就可以安全地採用批量收集優化。使用oracle10g及以上版本,資料庫引數plsql_optimize_level設定至少為2(預設),就會自動獲得這樣的優化。下面是一個例子:
SQL> -- 隱式批量獲取呼叫模式
SQL> SELECT banner FROM v$version WHERE ROWNUM=1; --查詢資料庫版本資訊
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
SQL> SELECT VALUE FROM v$parameter WHERE NAME='plsql_optimize_level'; --優化引數查詢
VALUE
--------------------------------------------------------------------------------
2
SQL> BEGIN
2 FOR i IN (
3 SELECT descr
4 FROM hardware
5 WHERE aisle=1
6 AND item BETWEEN 1 AND 500
7 )
8 LOOP
9 NULL;
10 dbms_output.put_line(i.descr);
11 END LOOP;
12 END;
13 /
PL/SQL procedure successfully completed
2.3 針對不同集合式資料型別的批量獲取的行儲存
2.3.1三種集合式的資料型別
n Varray(可變陣列)
n Nested table(巢狀表)
n Associative array(關聯陣列)
2.3.2 批量操作時間響應對比
(1)逐行程式碼與等價的批量操作響應時間對比
case1:逐行程式碼資料收集舉例
SQL> PURGE RECYCLEBIN; --;--清空當前使用者的回收站
Done
Executed in 0.125 seconds
SQL> alter system flush buffer_cache; ---清空快取
System altered
Executed in 0.203 seconds
SQL> SET timing ON --顯示時間
SQL> DECLARE
2 CURSOR c_tool_list IS
3 SELECT descr d1
4 FROM hardware;
5 l_descr hardware.descr%TYPE;
6 BEGIN
7 OPEN c_tool_list;
8 LOOP
9 FETCH c_tool_list INTO l_descr;
10 EXIT WHEN c_tool_list%NOTFOUND;
11 END LOOP;
12 CLOSE c_tool_list;
13 END;
14 /
PL/SQL procedure successfully completed
Executed in 14.509 seconds
case2:批量資料收集舉例
SQL> PURGE RECYCLEBIN; --;--清空當前使用者的回收站
Done
SQL> alter system flush buffer_cache; ---清空快取
System altered
SQL> SET timing ON --顯示時間
SQL> DECLARE
2 CURSOR c_tool_list IS
3 SELECT descr d2
4 FROM hardware;
5
6 TYPE t_descr_list IS TABLE OF c_tool_list%ROWTYPE;
7 l_descr_list t_descr_list;
8 BEGIN
9 OPEN c_tool_list;
10 FETCH c_tool_list BULK COLLECT INTO l_descr_list;
11 CLOSE c_tool_list;
12 END;
13 /
PL/SQL procedure successfully completed
Executed in 1.638 seconds
case3:自動批量收集優化舉例
SQL> PURGE RECYCLEBIN; --;--清空當前使用者的回收站
Done
SQL> alter system flush buffer_cache; ---清空快取
System altered
SQL> ALTER SESSION SET plsql_optimize_level=2;
Session altered
SQL> SET timing ON --顯示時間
SQL> BEGIN
2 FOR i IN(
3 SELECT descr d3
4 FROM hardware)
5 LOOP
6 NULL;
7 END LOOP;
8 END;
9
10 /
PL/SQL procedure successfully completed
Executed in 1.498 seconds
結論:從case1與case2執行效率可知,只是通過減少從資料庫提取資料的次數,就使它的速度快了8.86倍。case3使用自動批量收集優化提取1000000行,它的效能和使用完全批量收集一樣好,甚至更優。
(2)三種資料集合型別的批量操作響應時間對比
測試程式碼如下:
SQL> alter system flush buffer_cache; ---清空快取
System altered
SQL> SET timing ON --顯示時間
SQL> DECLARE
2 TYPE t_va IS VARRAY(1000) OF NUMBER;
3 TYPE t_nt IS TABLE OF NUMBER;
4 TYPE t_aa IS TABLE OF NUMBER INDEX BY PLS_INTEGER;
5
6 va t_va;
7 nt t_nt;
8 aa t_aa;
9 BEGIN
10 FOR i IN 1 ..10000 LOOP
11 SELECT ROWNUM
12 -- 在想要的測試集上解除註釋
13 BULK COLLECT INTO va
14 --BULK COLLECT INTO nt
15 --BULK COLLECT INTO aa
16 FROM dual
17 CONNECT BY LEVEL <=1000;
18 END LOOP;
19 END;
20 /
PL/SQL procedure successfully completed
Executed in 11.779 seconds
其它兩種情形(程式碼替換為:BULK COLLECT INTO nt和BULK COLLECT INTO aa),輸出分別為:
PL/SQL procedure successfully completed
Executed in 11.856 seconds
以及
PL/SQL procedure successfully completed
Executed in 11.794 seconds
由此可見,對於批量收集的效能來說,3中型別的集合效能比較接近。
3 監控批量收集的開銷
批量收集對資料庫會話有很顯著的影響,資料處理是把取出的資料放在一個緩衝區中,緩衝器在會話的記憶體中。PL/SQL集合將消耗記憶體,如果資料庫有大的集合或有大量的併發會話,PL/SQL記憶體消耗的控制就變得很有必要。
以下例子,將定義一個包含不同的獲取尺寸(fetch size)的陣列,它將用於從hardware表中提取一組行。在一次迭代中,將每次獲取5行,然後是10行,50行等等。程式碼及執行結果如下:
SQL> SET serverout ON
SQL> DECLARE
2 TYPE t_row_list IS TABLE OF hardware.descr%TYPE;
3 l_rows t_row_list;
4
5 l_pga_ceiling NUMBER(10);
6
7 TYPE t_fetch_size IS TABLE OF PLS_INTEGER;
8 l_fetch_sizes t_fetch_size := t_fetch_size(5,10,100,500,1000,10000,100000,1000000);
9 rc SYS_REFCURSOR;
10 BEGIN
11 SELECT VALUE
12 INTO l_pga_ceiling
13 FROM v$mystat m, v$statname s
14 WHERE s.STATISTIC#=m.STATISTIC#
15 AND s.NAME='session pga memory max';
16
17 dbms_output.put_line('Initial PGA: '||l_pga_ceiling);
18 FOR i IN 1..l_fetch_sizes.count
19 LOOP
20 OPEN rc FOR SELECT descr FROM hardware;
21 LOOP
22 FETCH rc BULK COLLECT INTO l_rows LIMIT l_fetch_sizes(i);
23 EXIT WHEN rc%NOTFOUND;
24 END LOOP;
25 CLOSE rc;
26
27 SELECT VALUE
28 INTO l_pga_ceiling
29 FROM v$mystat m, v$statname s
30 WHERE s.STATISTIC#=m.STATISTIC#
31 AND s.name='session pga memory max';
32
33 dbms_output.put_line('Fetch size: '||l_fetch_sizes(i));
34 dbms_output.put_line('- PGA Max: '||l_pga_ceiling);
35 END LOOP;
36 END;
37
38 /
Initial PGA: 3060088
Fetch size: 5
- PGA Max: 3191160
Fetch size: 10
- PGA Max: 3191160
Fetch size: 100
- PGA Max: 3191160
Fetch size: 500
- PGA Max: 3191160
Fetch size: 1000
- PGA Max: 3191160
Fetch size: 10000
- PGA Max: 4174200
Fetch size: 100000
- PGA Max: 14528888
Fetch size: 1000000
- PGA Max: 118403448
PL/SQL procedure successfully completed
Executed in 10.623 seconds
從輸出結果可以看出,獲取尺寸超過100000行,PGA消耗從2.9MB增長到112.9MB(在一次fetch呼叫中批量收集整個1000000行的集合)。如果有數百個會話,每個會話都消耗數百兆,那麼肯定對可擴充套件性是一種威脅。因此,使用批量操作時,在效能和記憶體消耗之間取得平衡是必需的,建議在不知道未來的結果集的大小(或近似大小)時,不要對結果集發出沒有帶Limit子句的fetch bulk collect語句。
4 總結
通過本文的程式算例,可以看到批量SQL操作帶來的大的效能優勢。而從以“行”為中心獲取資料轉變為以“集合”為中心的批量獲取資料,所需的工作量也很小。只需從如下兩方面著手:
n 如果是使用了for cursor_variable in(查詢或遊標)的語法,那麼實現批量收集,只需使用較新版的oracle(oracle10g以上)即可,並保證其PL/SQL編譯設定是按預設值設定的。
n 否則,也只是一些簡單的重新編碼問題,將fetch cursor into風格改變為fetch cursor bulk collect into即可。只需增加幾個關鍵字和一些PL/SQL的型別定義,就可以實現程式碼的改寫。
所以,這種轉變的工作量小、風險低,但帶來的效率提升是大的。這種以集合為中心的想法,在Oracle技能集的其它領域均有體現,如:管道表函式等大規模資料處理。通常情形為使用SQL會比過程性邏輯得到更多效能優勢,利用批量操作可以帶來效率的大幅提升。