使用bulk collect 和 forall 提高遊標效能
當執行一個pl/sql程式時, pl/sql語句引擎會執行pl/sql語句。但如果在這個過程中引擎遇到sql語句,它會把這個語句傳給sql引擎(後臺發生上下文切換)。
在PL/SQL和SQL引擎(engines)中,太多的上下文切換(context switches)會影響效能。這個會發生在當一個迴圈為集合中的每個元素執行一個單個SQL語句時。而使用批挷定能顯著提高效能。
在SQL語句中,為PL/SQL變數指定值稱為挷定(binding),DML語句能傳遞所有集合元素到一個單個操作中,這過程稱為批挷定(bulk binding)。 如果集合有20個元素,批挷定讓你用單個操作等效於執行與
作用:BULK COLLECT提供對資料的高速檢索。
優點:可以將多個行引入一個或多個集合中,而不是單獨變數或記錄中,減少了上下文切換,效能高。
缺點:1.消耗更多的記憶體(PGA);由於該數集合據儲存在每個會話中,假設一個會話多消耗
2.BULK COLLECT INTO的目標物件必須是集合型別。
3.不能對使用字串型別作鍵的關聯陣列使用BULK COLLECT子句。
4.如果有一個隱式的資料型別轉換,複合目標的集合(如物件型別集合)就不能用於BULK COLLECTINTO 子句中。
bulk collect語句可以使用三種方式:
1在select into語句中使用bulk collect
2在fetch into中使用bulk collect
3在returning into中使用bulk collect
create table empl_tbl(last_name varchar2(20), first_name varchar2(10), salary number(10)); --建立表 create table demo_t( id number(5), name varchar2(50), sales number );
--在select into語句中使用bulk collect
DECLARE
TYPE contractList IS TABLE OF sad.cm_contracts_t.contract_number%TYPE;
contracts contractList;
BEGIN
-- Limit the number of rows to 100.
SELECT contract_number BULK COLLECT INTO contracts FROM sad.cm_contracts_t
WHERE ROWNUM <= 100;
--在fetch into中使用bulk collect
DECLARE
TYPE contracts_list IS TABLE OF sad.cm_contracts_t%ROWTYPE;
contracts_l contracts_list;
CURSOR c1 IS
SELECT contract_number,contract_id FROM contracts_l WHERE deptno > 10;
BEGIN
OPEN c1;
FETCH c1 BULK COLLECT INTO dept_recs;
END;
--在returning into中使用bulk collect
--CREATE TABLE item AS SELECT * FROM sad.sad_prm_item_ti;
declare
-- Local variables here
TYPE lineid IS TABLE OF item.lineid%TYPE;
line_id lineid;
begin
DELETE FROM item t WHERE t.lineid='1'
RETURNING lineid BULK COLLECT INTO line_id;
dbms_output.put_line('Deleted ' || SQL%ROWCOUNT || ' rows:');
FOR i IN line_id.FIRST .. line_id.LAST
LOOP
dbms_output.put_line('item : '||line_id(i));
END LOOP;
end;
-- Created on 2013/12/4 by ZWX190516
DECLARE
-- Local variables here
all_row NUMBER(10);
all_rows_bluk NUMBER(10);
temp_last_name empl_tbl.last_name%TYPE;
--首先,定義一個Index-by表資料型別
TYPE last_name_tab IS TABLE OF empl_tbl.last_name%TYPE INDEX BY BINARY_INTEGER;
last_name_arr last_name_tab;
--定義一個Index-by表集合變數
dis_count NUMBER;
--資料量
data_count NUMBER :=100000;
--記錄時間
t1 NUMBER;
t2 NUMBER;
t3 NUMBER;
t4 NUMBER;
BEGIN
all_row := 0;
all_rows_bluk := 0;
temp_last_name := ' ';
--寫入1W筆資料
FOR i IN 1 .. data_count LOOP
INSERT INTO empl_tbl
(last_name, first_name, salary)
VALUES
('carl' || (i), 'wu' || (data_count-1), i);
END LOOP;
COMMIT;
--記錄時間
SELECT DBMS_UTILITY.get_time INTO t1 FROM DUAL;
--查詢不相同資料使用時間
SELECT COUNT(DISTINCT last_name) "Distinct Last Name"
INTO dis_count
FROM empl_tbl;
--記錄時間
SELECT DBMS_UTILITY.get_time INTO t2 FROM DUAL;
--使用簡單遊標實現
BEGIN
FOR cur IN (SELECT last_name FROM empl_tbl ORDER BY last_name) LOOP
IF cur.last_name != temp_last_name THEN
all_row := all_row + 1;
END IF;
temp_last_name := cur.last_name;
END LOOP;
dbms_output.put_line('all_rows are ' || all_row);
END;
--記錄時間
SELECT DBMS_UTILITY.get_time INTO t3 FROM DUAL;
--使用Bulk Collect來實現
BEGIN
SELECT last_name BULK COLLECT INTO last_name_arr FROM empl_tbl;
FOR i IN 1 .. last_name_arr.count LOOP
IF temp_last_name != last_name_arr(i) THEN
all_rows_bluk := all_rows_bluk + 1;
END IF;
temp_last_name := last_name_arr(i);
END LOOP;
dbms_output.put_line(' BULK COLLECT all_rows are ' || all_rows_bluk);
END;
--記錄時間
SELECT DBMS_UTILITY.get_time INTO t4 FROM DUAL;
DBMS_OUTPUT.put_line('Execution Time (hsecs)');
DBMS_OUTPUT.put_line('---------------------');
DBMS_OUTPUT.put_line('distince: ' || TO_CHAR(t2 - t1));
DBMS_OUTPUT.put_line('cursor one by one : ' || TO_CHAR(t3 - t2));
DBMS_OUTPUT.put_line('cursor BULK COLLECTe : ' || TO_CHAR(t4 - t3));
END;
FORALL
作用:FORALL可大大改進INSERT、UPDATE和DELETE操作的效能。
在傳送語句到SQL引擎前,FORALL語句告知PL/SQL引擎批挷定輸入集合。儘管FORALL語句包含一個迭代(iteration)模式,它並不一是個FOR迴圈。
優點:一次性繫結資料寫進行操作。減少上下文切換,提高資料庫效能。
缺點:1.操作邏輯單一,只能寫一個sql。
2.若出現異常,必須捕獲異常後提交才能保證資料正常操作。
forall語句使用三種方式(只允許一條sql):
1.FORALL 下標變數(只能當作下標被引用) IN下限..上限。
2.INDICES OF collection_name (引用特定集合元素的下標(該集合可能為稀疏))。
3.VALUES OF colletion_name(把該集合中的值當作下標,且該集合值的型別只能是PLS_INTEGER BINARY_INTEGER)
)。
--批量插入演示簡單;必須順序
declare
type tb_table_type is table of demo_t%rowtype
index by binary_integer;
tb_table tb_table_type;
begin
for i in 1..10 loop
tb_table(i).id:=i;
tb_table(i).name:='NAME'||i;
end loop;
forall i in 1..tb_table.count
insert into demo_t values tb_table(i);
end;
--批量修改演示
declare
type demo_t_type is table of demo_t%rowtype
index by binary_integer;
demo_t demo_t_type;
begin
for i in 1..10 loop
demo_t(i).id:=i;
demo_t(i).name:='NAMES'||i;
end loop;
forall i in 1..demo_t.count
update tb1 t set row = demo_t(i) where t.id = demo_t(i).id;
end;
--批量刪除演示
declare
type demo_t_type is table of demo_t%rowtype
index by binary_integer;
demo_t demo_t_type;
begin
for i in 1..10 loop
demo_t(i).id:=i;
demo_t(i).name:='NAMES'||i;
end loop;
forall i in 1..demo_t.count
delete tb1 where id = demo_t(i).id;
end;
select * from demo_t
--批量寫入不連續的陣列
declare
type demo_t_table_type is table of demo_t%rowtype
index by binary_integer;
demo_t_table demo_t_table_type;
begin
for i in 1..10 loop
demo_t_table(i).id:=i;
demo_t_table(i).name:='NAME'||i;
end loop;
demo_t_table.delete(3);
demo_t_table.delete(6);
demo_t_table.delete(9);
forall i in indices of demo_t_table
insert into demo_t values demo_t_table(i);
end;
--按照下標寫入陣列資料
declare
type index_poniter_type is table of pls_integer;
index_poniter index_poniter_type;
type demo_t_table_type is table of demo_t%rowtype
index by binary_integer;
demo_t_table demo_t_table_type;
begin
index_poniter:=index_poniter_type(1,3,5,7);
for i in 1..10 loop
demo_t_table(i).id:=i;
demo_t_table(i).name:='NAME'||i;
end loop;
forall i in values of index_poniter
insert into demo_t values demo_t_table(i);
end;
-- Created on 2013/12/5 by ZWX190516
DECLARE
-- Local variables here
TYPE numtab IS TABLE OF NUMBER(20) INDEX BY BINARY_INTEGER;
TYPE nametab IS TABLE OF VARCHAR2(50) INDEX BY BINARY_INTEGER;
pnums numtab;
pnames nametab;
t1 NUMBER;
t2 NUMBER;
t3 NUMBER;
BEGIN
FOR j IN 1 .. 1000000 LOOP
pnums(j) := j;
pnames(j) := 'Seq No. ' || TO_CHAR(j);
END LOOP;
--記錄時間
SELECT DBMS_UTILITY.get_time INTO t1 FROM DUAL;
--普通遊標寫入
FOR i IN 1 .. 1000000 LOOP
INSERT INTO blktest VALUES (pnums(i), pnames(i));
END LOOP;
--記錄時間
SELECT DBMS_UTILITY.get_time INTO t2 FROM DUAL;
--forall寫入
FORALL i IN 1 .. 1000000
INSERT INTO blktest VALUES (pnums(i), pnames(i));
--記錄時間
SELECT DBMS_UTILITY.get_time INTO t3 FROM DUAL;
DBMS_OUTPUT.put_line('Execution Time (hsecs)');
DBMS_OUTPUT.put_line('---------------------');
DBMS_OUTPUT.put_line('FOR loop: ' || TO_CHAR(t2 - t1));
DBMS_OUTPUT.put_line('FORALL: ' || TO_CHAR(t3 - t2));
END;
異常:Forall在出現異常情況後,捕獲到異常不進行事物處理,那麼將會自動回滾。
其他用法:
1.用%BULK_ROWCOUNT屬性計算FORALL迭代影響行數。
1.2用%BULK_EXCEPTIONS屬性處理FORALL異常
CREATE TABLE emp2 (deptno NUMBER(2), job VARCHAR2(15));
INSERT INTO emp2 VALUES(10, 'Clerk');
INSERT INTO emp2 VALUES(10, 'Clerk');
INSERT INTO emp2 VALUES(20, 'Bookkeeper'); -- 10-char job title
INSERT INTO emp2 VALUES(30, 'Analyst');
INSERT INTO emp2 VALUES(30, 'Analyst');
Comit;
DECLARE
TYPE NumList IS TABLE OF NUMBER;
depts NumList := NumList(10, 20, 30);
BEGIN
FORALL j IN depts.FIRST..depts.LAST
UPDATE emp2 SET job = job || ' (temp)'
WHERE deptno = depts(j);
-- raises a "value too large" exception
EXCEPTION
WHEN OTHERS THEN
COMMIT;
END;
PL/SQL Developer Test script 3.0
12
DECLARE
TYPE NumList IS TABLE OF NUMBER;
depts NumList := NumList(10, 20, 50);
BEGIN
FORALL j IN depts.FIRST..depts.LAST
UPDATE demo_t t SET sales = sales * 1.10 WHERE t.id=depts(j);
dbms_output.put_line( SQL%BULK_ROWCOUNT(3) );
END;
在進行SQL資料操作語句時,SQL引擎開啟一個隱式遊標(命名為SQL),該遊標的標量屬性(scalar attribute)有 %FOUND, %ISOPEN, %NOTFOUND, and %ROWCOUNT。
FORALL語句除具有上邊的標量屬性外,還有個複合屬性(composite attribute):%BULK_ROWCOUNT,該屬性具有索引表(index-by table)語法。它的第i個元素存貯SQL語句(INSERT, UPDATE或DELETE)第i個執行的處理行數。如果第i個執行未影響行,%bulk_rowcount (i),返回0。FORALL與%bulk_rowcount屬性使用相同下標。
0
0
PL/SQL Developer Test script 3.0
27
DECLARE
TYPE NumList IS TABLE OF NUMBER;
num_tab NumList := NumList(10,0,11,12,30,0,20,199,2,0,9,1);
errors NUMBER;
dml_errors EXCEPTION;
PRAGMA exception_init(dml_errors, -24381);
BEGIN
FORALL i IN num_tab.FIRST..num_tab.LAST SAVE EXCEPTIONS
DELETE FROM demo_t WHERE sales > 500000/num_tab(i);
EXCEPTION
WHEN dml_errors THEN
errors := SQL%BULK_EXCEPTIONS.COUNT;
dbms_output.put_line('Number of errors is ' || errors);
FOR i IN 1..errors LOOP
dbms_output.put_line('Error ' || i || ' occurred during '||
'iteration ' || SQL%BULK_EXCEPTIONS(i).ERROR_INDEX);
dbms_output.put_line('Oracle error is ' ||
SQLERRM(-SQL%BULK_EXCEPTIONS(i).ERROR_CODE));
END LOOP;
END;
執行期間引發的所有異常都被儲存遊標屬性 %BULK_EXCEPTIONS中,它存貯一個集合記錄,每記錄有兩個欄位:
%BULK_EXCEPTIONS(i).ERROR_INDEX:存貯在引發異常期間FORALL語句迭代(重複:iteration)
%BULK_EXCEPTIONS(i).ERROR_CODE:存貯相應的Oracle錯誤程式碼
%BULK_EXCEPTIONS.COUNT存貯異常的數量。(該屬性不是%BULK_EXCEPTIONS集合記錄的欄位)。如果忽略SAVE EXCEPTIONS,當引發異常時,FORALL語句停止執行。此時,SQL%BULK_EXCEPTIONS.COUNT 返回1, 且SQL%BULK_EXCEPTIONS只包含一條記錄。如果執行期間無異常 SQL%BULK_EXCEPTIONS.COUNT 返回 0
注意: 1.bulk collect 消耗的是PGA記憶體。每個會話都會佔記憶體,所以儘可能使用limit限制。
2.當pl/sql優化等級為2(預設)或更高,把遊標內容直接寫在for迴圈之中有利於提高效能,除非其他特殊情況。
3.注意cursor%notfond的正確使用。
PL/SQL Developer Test script 3.0
26
-- Created on 2013/11/25 by ZWX190516
declare
-- Local variables here
i integer;
TYPE type_t IS TABLE OF sad.cm_contracts_t%ROWTYPE;
type_table type_t;
CURSOR cur_test IS
SELECT * FROM sad.cm_contracts_t t
WHERE rownum=1;
begin
-- Test statements here
OPEN cur_test;
LOOP
FETCH cur_test BULK COLLECT INTO type_table LIMIT 3;
EXIT WHEN cur_test%NOTFOUND; --該句判斷放在前邊,導致漏掉資料。
FOR i IN type_table.first..type_table.last
LOOP
dbms_output.put_line(type_table(i).contract_id);
END LOOP;
END LOOP;
CLOSE cur_test;
end;
0
0