Oracle為PL/SQL中的SQL相關功能提供了FORALL語句和BULK COLLECT子句,顯著的增強了SQL相關功能。這兩個語句一起被稱作PL/SQL的批處理語句。Oracle為什麽要提供這兩個語句呢?我們首先了解一下PL/SQL的引擎。該引擎可以安裝在數據庫,或者應用開發工具上,例如Oracle Froms。當PL/SQL運行引擎執行一個代碼塊時,引擎本身只會處理過程語句,而SQL語句是發送給SQL引擎執行。SQL語句的執行時是由數據庫的SQL引擎負責,再將執行結果返回給PL/SQL引擎。
以下是PL/SQL引擎運行原理:
這種PL/SQL引擎和SQL引擎之間的控制轉移叫做上下文切換。每次發生切換時,都會有額外的開銷。通過FORALL語句和BULK COLLECT子句,可以把兩個引擎的通行進行微調,讓PL/SQL更有效地把多個上下文切換壓縮成一個切換,從而提升程序的性能。
1.通過BULK COLLECT加速查詢
不管是顯示遊標還是隱式遊標,都可以通過BULK COLLECT在數據庫的單次交互中獲取多行數據。BULKCOLLECT減少了PL/SQL引擎和SQL引擎之間的切換次數,因此也減少了提取數據時的額外開銷。
創建一張測試數據表:create table my_objects as select * from user_objects;<喎�"http://www.bitsCN.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+z9bU2tDo0qq00215X29iamVjdHOx7czhyKHL+dPQyv2+3SzO0sPHzaizo7XE1/a3qMjnz8KjujwvcD4KPHA+LS1GT1LTzrHqzOHIob7dPC9wPgo8cD5kZWNsYXJlPGJyPgogIHR5cGUgbnRfb2JqZWN0IGlzIHRhYmxlIG9mIG15X29iamVjdHMlcm93dHlwZTs8YnI+CiAgdm50X29iamVjdCAgIG50X29iamVjdCA6PSBudF9vYmplY3QoKTsgLS2z9cq8u688YnI+CiAgdl9jb3VudCAgICAgIG51bWJlciA6PSAwOzxicj4KICBjX2JpZ19udW1iZXIgbnVtYmVyIDo9IHBvd2VyKDIsIDMxKTs8YnI+CiAgbF9zdGFydF90aW1lIHBsc19pbnRlZ2VyOzxicj4KYmVnaW48YnI+CiAgZGJtc19vdXRwdXQucHV0X2xpbmUo"========FOR遊標提取==========');
l_start_time := dbms_utility.get_time;
for vrt_object in (select * from my_objects) loop
vnt_object.extend;
vnt_object(vnt_object.last) := vrt_object;
end loop;
dbms_output.put_line('count=' || vnt_object.count);
dbms_output.put_line('Elapsed: ' ||
to_char(mod(dbms_utility.get_time - l_start_time +
c_big_number,
c_big_number)));
end;
--顯示遊標提取
declare
type nt_object is table of my_objects%rowtype;
vnt_object nt_object := nt_object(); --初始化
c_big_number number := power(2, 31);
l_start_time pls_integer;
cursor cur_object is
select * from my_objects;
vrt_object cur_object%rowtype;
begin
dbms_output.put_line('========顯示遊標提取==========');
l_start_time := dbms_utility.get_time;
open cur_object;
loop
fetch cur_object
into vrt_object;
exit when cur_object%notfound;
vnt_object.extend;
vnt_object(vnt_object.last) := vrt_object;
end loop;
close cur_object;
dbms_output.put_line('count=' || vnt_object.count);
dbms_output.put_line('Elapsed: ' ||
to_char(mod(dbms_utility.get_time - l_start_time +
c_big_number,
c_big_number)));
end;
結果:FOR遊標明顯要優於顯示遊標
註意:要使用集合嵌套表,必須初始化。
這個代碼毫無疑問可以完成任務,不過可能會花費很長的時間。假設my_objects表中有1000個記錄,PL/SQL引擎就要向SGA中的遊標發送10000個fetch操作。
為了幫組這種場景,可以在查詢語句中的INTO元素中使用BULK COLLECT子句。對於遊標使用這個子句是告訴SQL引擎把查詢出來的多行數據庫批量綁定到指定的集合上。然後再把控制返回給PL/SQL引擎。這個子句的語法是:
... BULK COLLECT INTO collection_name[,collection_name] ...
其中collection_name代表一個集合。
使用BULK COLLECT時,要記住以下這些規則和限制:
在Oracle 9i數據之前,只能在靜態SQL中使用BULK COLLECT。現在不論是動態還是靜態SQL都可以使用BULK COLLECT。可以在下面這些語句中使用BULK COLLECT:SELECT INTO,FETCH INTO和RETURNING INTO。對於在BULK COLLECT子句中使用的集合,SQL引擎會自動進行初始化及擴展。它會從索引1開始填充集合,連續的插入元素(緊湊的),把之前已經使用的元素的值覆蓋。不能在FORALL語句中使用SELECT...BULK COLLECT語句。如果SELECT...BULK COLLECT沒有找到任何行,不會拋出NO_DATA_FOUND異常。相反,我們必須對集合的內容進行檢查看看其中到底有沒有數據。如果查詢沒有返回任何行,集合的COUNT方法將返回0。1.1使用隱式遊標
使用隱式遊標(SELECT INTO)重寫,並使用dbms_utility.get_time獲取時間。
declare
type nt_object is table of my_objects%rowtype;
vnt_object nt_object; --未初始化
c_big_number number := power(2, 31);
l_start_time pls_integer;
begin
dbms_output.put_line('========BULK COLLECT批量提取==========');
l_start_time := dbms_utility.get_time;
select * bulk collect into vnt_object from my_objects;
dbms_output.put_line('count=' || vnt_object.count);
dbms_output.put_line('Elapsed: ' ||
to_char(mod(dbms_utility.get_time - l_start_time +
c_big_number,
c_big_number)));
end;
1.2使用顯示遊標
使用顯示遊標重寫:
declare
type nt_object is table of my_objects%rowtype;
vnt_object nt_object := nt_object(); --初始化
c_big_number number := power(2, 31);
l_start_time pls_integer;
cursor cur_object is
select * from my_objects;
begin
dbms_output.put_line('========顯示遊標BULK COLLECT提取==========');
l_start_time := dbms_utility.get_time;
open cur_object;
fetch cur_object bulk collect
into vnt_object;
close cur_object;
dbms_output.put_line('count=' || vnt_object.count);
dbms_output.put_line('Elapsed: ' ||
to_char(mod(dbms_utility.get_time - l_start_time +
c_big_number,
c_big_number)));
end;
1.3限制BULK COLLECT提取數據
Oracle為BULK COLLECT提供了一個LIMIT子句,讓我們可以對從數據庫提取的行的數量做限制,語法是:
FETCH cursor BULK COLLECT INTO ... [LIMIT rows]
其中rows可是直接量、變量或者求值的結果是整數的表達式。
對於BULK COLLECT來說,LIMIT是非常有用的,因為這個語句可以幫助我們控制程序用多大內存來處理數據。比如,假設你需要查詢並處理10000行的數據。你可以用BULK COLLECT一次取出所有的行,然後填充到一個非常大的集合中。可是,這種方法會消耗掉該會話的大量PGA內存。如果這個代碼被多個Oracle模式運行,你的應用程序性能就可能會因為PGA換頁而下降。
declare
type nt_object is table of my_objects%rowtype;
vnt_object_bulk nt_object;
vnt_object nt_object := nt_object(); --初始化
c_big_number number := power(2, 31);
l_start_time pls_integer;
cursor cur_object is
select * from my_objects;
begin
dbms_output.put_line('========顯示遊標BULK COLLECT LIMIT提取==========');
l_start_time := dbms_utility.get_time;
open cur_object;
loop
fetch cur_object bulk collect
into vnt_object_bulk limit 100;
for i in vnt_object_bulk.first .. vnt_object_bulk.last loop
vnt_object.extend;
vnt_object(vnt_object.last) := vnt_object_bulk(i);
end loop;
exit when cur_object%notfound;
end loop;
close cur_object;
dbms_output.put_line('count=' || vnt_object.count);
dbms_output.put_line('Elapsed: ' ||
to_char(mod(dbms_utility.get_time - l_start_time +
c_big_number,
c_big_number)));
end;
註意:這裏是在循環的最後通過檢查cur_object%notfound的值來結束循環。當每次只查詢一條數據時,總是把這個代碼緊跟在FETCH語句的後面。不過使用BULK COLLECT時就不能這麽做了,因為當FETCH操作提取最後一部分數據集之後,遊標雖然空了(%NOTFOUND會返回TRUE)但是在集合中還有一些元素需要處理。因此,或者在循環的最後檢查%NOTFOUND屬性,或者在FETCH操作之後立即查看集合的內容:
open cur_object;
loop
fetch cur_object bulk collect
into vnt_object_bulk limit 100;
exit when vnt_object_bulk.count = 0;
和在循環體的最後檢查%NOTFOUND屬性值比較起來,第二中方法的不好之處就在於我們需要額外再執行一個返回空行的FETCH操作。
2.通過FORALL加速DML
BULK COLLECT用於對查詢加速。而FORALL會對插入、更新、刪除以及合並做同樣的事情(只有Oracle 11g才支持FORALL的合並)。FORALL告訴PL/SQL引擎要先把一個或者多個集合的所有成員都綁定到SQL語句中,然後在把語句發送給SQL引擎。
2.1FORALL語句的語法
盡管FORALL語句帶有一個叠代模式,但它並不是一個FOR循環。因此,既不需要LOOP也不需要END LOOP語句。它的語法如下:
FORALL index IN
[lower.bound .. upper.bound |
INDICES OF indexing_collection |
VALUES OF indexing_collection
]
[SAVE EXCEPTIONS]
sql_statement;
其中:
index
是一個整數,由Oracle隱式聲明的,並被定義做集合的索引值。
lower_bound
操作開始的索引值。
upper_bound
操作結束的索引值。
sql_statement
將對每一個集合元素執行的SQL語句。
indexing_collection
這是一個PL/SQL集合,是一個指向sql_statement所使用的綁定數組的索引的集合。INDICES OF和VALUES OF是從Oracle 10g才有的。
SAVE EXCEPTIONS
這是一個可選的子句,告訴FORALL處理全部行,不過把發生的任何異常保存下來。
使用FORALL時,必須遵守這些規則:
FORALL語句的主體必須是一個單獨的DML語句——可以是一個插入、更新、刪除或者合並操作(Oracle 11g及以後版本)。上邊界和下邊界對於SQL語句所使用的集合來說,必須是一個有效的連續索引值範圍。DML語句中使用的集合下標不能是表達式。2.2FORALL批量插入
從user_objects數據字典中中批量將所有數據插入到my_objects表中。
declare
type nt_object is table of my_objects%rowtype;
vnt_object nt_object;
c_big_number number := power(2, 31);
l_start_time pls_integer;
begin
dbms_output.put_line('========批量插入==========');
l_start_time := dbms_utility.get_time;
select * bulk collect into vnt_object from user_objects;
forall i in vnt_object.first .. vnt_object.last
insert into my_objects values vnt_object (i);
dbms_output.put_line('count=' || vnt_object.count);
dbms_output.put_line('Elapsed: ' ||
to_char(mod(dbms_utility.get_time - l_start_time +
c_big_number,
c_big_number)));
end;
提示:在Oracle 10g及以後的版本中,PL/SQL編輯會自動對FOR遊標循環進行優化,從而性能可以和BULK COLLECT相媲美。
參考:
Oracle PL/SQL程序設計(第五版) Steven Feuersterin & Bill Pribyl著 張曉明譯
Tags:
文章來源: