過程插入更新的3種方法:
a、逐條檢查插入或更新,同時執行插入或更新
b、逐條merge into(逐條是為了記錄過程日志與錯誤資訊)
c、基于關聯數組的檢查插入、更新,通過forall批量sql執行
以下為模拟步驟:
1、建立模拟大表,資料量1億不分區
create table big_table as
SELECT ROWNUM RN,'A' A,'B' B,'C' C,'D' D FROM
( SELECT ROWNUM RN FROM DUAL CONNECT BY ROWNUM <= 1000) T1,
( SELECT ROWNUM RN FROM DUAL CONNECT BY ROWNUM <= 1000) T2,
( SELECT ROWNUM RN FROM DUAL CONNECT BY ROWNUM <= 100) T3;
說明:用connect by方法建立測試大表相對較快
2、給大表加上一個主鍵,因為插入更新基于主鍵
alter table BIG_TABLE2
add constraint PK_RN2 primary key (RN)
3、建立模拟的更新表,資料量20萬
CREATE TABLE UP_TABLE ( RN NUMBER,A VARCHAR2(20),B VARCHAR2(20),C VARCHAR2(20),D VARCHAR2(20))
4、給更新表加資料,10萬更新,10萬插入,寫的相對煩人些。。。代碼不貼出來了
5、日志表結構
-- Create table
create table INS_UP_LOG
(
type VARCHAR2(50),
time NUMBER,
num NUMBER,
up NUMBER,
ins NUMBER
)
6、第一種方法的過程如下:
create or replace procedure sp_ins_up is
v_bz char(1);--插入更新标志
v_stime pls_integer;--過程起始時間
i pls_integer := 0; --執行數量
v_up pls_integer := 0;--更新數量
v_ins pls_integer := 0;--插入數量
v_time pls_integer;--循環目前循環起始時間
begin
v_stime := dbms_utility.get_time();--過程起始時間
v_time := dbms_utility.get_time();--目前循環起始時間
for rec in (select * from up_table) loop--開始循環
i := i + 1;
begin--目前資料執行插入更行判斷
select '1' into v_bz from big_table1 a where a.rn = rec.rn;
exception
when no_data_found then
v_bz := 0;
end;
if v_bz = 1 then--更新
v_up := v_up + 1;
update big_table1 a
set a.a = rec.a, a.b = rec.b, a.c = rec.c, a.d = rec.d
where a.rn = rec.rn;
else--插入
v_ins := v_ins + 1;
insert into big_table1 values (rec.rn, rec.a, rec.b, rec.c, rec.d);
end if;
if mod(i, 5000) = 0 then--一定數量送出
insert into INS_UP_LOG
values
('sp_ins_up',
round((dbms_utility.get_time - v_time) / 100, 2),
v_up + v_ins,
v_up,
v_ins);
commit;
v_up := 0;
v_ins := 0;
v_time := dbms_utility.get_time;
end if;
end loop;
commit;
insert into ins_up_log--過程總時間記錄
values
('sp_ins_up',
round((dbms_utility.get_time - v_stime) / 100, 2),
'',
'',
'');
commit;
end sp_ins_up;
7、merge into方法過程如下:
create or replace procedure sp_merge is
v_stime pls_integer; --過程起始時間
i pls_integer := 0; --執行數量
v_time pls_integer; --循環目前循環起始時間
v_rn up_table.rn%type; --緩存值
v_errmsg varchar2(500); --目前錯誤資訊
begin
v_stime := dbms_utility.get_time();
v_time := dbms_utility.get_time();
for rec in (select * from up_table) loop
i := i + 1;
v_rn := rec.rn; --緩存目前主鍵
merge into big_table3 t --merge into
using (select * from up_table where rn = rec.rn) a
on (t.rn = a.rn)
when matched then
update set t.a = a.a, t.b = a.b, t.c = a.c, t.d = a.d
when not matched then
insert values (a.rn, a.a, a.b, a.c, a.d);
v_errmsg := sqlerrm;
if mod(i, 5000) = 0 then
insert into ins_up_log
values
('sp_merge',
round((dbms_utility.get_time - v_time) / 100, 2),
i,
i / 2, --此處插入數量不必在意
i / 2);
commit;
v_time := dbms_utility.get_time();
end if;
end loop;
insert into ins_up_log --過程總時間記錄
values
('sp_merge',
round((dbms_utility.get_time - v_stime) / 100, 2),
i,
'',
'');
commit;
exception
when others then
insert into ins_up_err values ('sp_merge', v_rn, v_errmsg);
end sp_merge;
8、forall方法,結合第一種方法的判斷方式:
create or replace procedure sp_forall_new is
type table_type is table of up_table%rowtype index by pls_integer; --增量表緩存
tab_all table_type; --增量表全
tab_up table_type; --增量表-更新
tab_ins table_type; --增量表-插入
v_bz char(1); --存在标志
cursor cur_up is
select * from up_table; --更新表遊标
v_stime pls_integer; --過程開始時間
v_time pls_integer; --每次循環開始時間
v_num_ins pls_integer := 0; --每次循環插入數量
v_num_up pls_integer := 0; --每次循環更新數量
v_num number; --數量緩存值
v_err_msg number; --forall中錯誤資訊
begin
v_stime := dbms_utility.get_time(); --過程開始
open cur_up; --打開遊标
loop
--開始循環遊标
v_time := dbms_utility.get_time(); --目前循環開始時間
v_num_ins := 0; --每次循環置零
v_num_up := 0; --每次循環置零
fetch cur_up bulk collect
into tab_all limit 5000; --批量插入緩存表,每次限定5000條
exit when tab_all.count = 0; --當緩存表中無資料,退出循環
--
for i in 1 .. tab_all.count loop
--此循環功能:判斷是否存在目标表中
begin
select '1' into v_bz from big_table2 t where t.rn = tab_all(i).rn; --存在
exception
when no_data_found then
v_bz := '0'; --不存在
end;
--
if v_bz = '1' then
--存在
v_num_up := v_num_up + 1; --更新數值+1
tab_up(v_num_up) := tab_all(i); --複制到更新緩存表
else
--不存在
v_num_ins := v_num_ins + 1; --插入數值+1
tab_ins(v_num_ins) := tab_all(i); --複制到插入緩存表
end if;
--
end loop;
--
--批量SQL,且在批量執行中錯誤的資訊記錄在sql%bulk_exceptions緩存表中
if tab_up.count <> 0 then
forall i in tab_up.first .. tab_up.last save exceptions
update big_table2 t
set t.a = tab_up(i).a,
t.b = tab_up(i).b,
t.c = tab_up(i).c,
t.d = tab_up(i).d
where t.rn = tab_up(i).rn;
for i in 1 .. sql%bulk_exceptions.count loop
v_num := sql%bulk_exceptions(i).error_index;
v_err_msg := sqlerrm(-sql%bulk_exceptions(i).error_code);
insert into ins_up_err
values
('sp_forall', tab_up(v_num).rn, v_err_msg);
end loop;
commit;
end if; --更新批量結束
--批量SQL,且在批量執行中錯誤的資訊記錄在sql%bulk_exceptions緩存表中
if tab_ins.count <> 0 then
forall i in tab_ins.first .. tab_ins.last save exceptions
insert into big_table2
values
(tab_ins(i).rn,
tab_ins(i).a,
tab_ins(i).b,
tab_ins(i).c,
tab_ins(i).d);
for i in 1 .. sql%bulk_exceptions.count loop
v_num := sql%bulk_exceptions(i).error_index;
v_err_msg := sqlerrm(-sql%bulk_exceptions(i).error_code);
insert into ins_up_err
values
('sp_forall', tab_ins(v_num).rn, v_err_msg);
end loop;
commit;
end if; --插入批量結束
insert into ins_up_log --記錄日志
values
('sp_forall',
round((dbms_utility.get_time - v_time) / 100, 2),
5000,
v_num_up,
v_num_ins);
commit;
--清空目前循環插入、更新緩存表資料(不清空,下次循環重複執行)
tab_up.delete;
tab_ins.delete;
end loop;
close cur_up; --關閉遊标
insert into ins_up_log --過程總時間記錄
values
('sp_forall',
round((dbms_utility.get_time - v_stime) / 100, 2),
'',
'',
'');
commit;
end sp_forall_new;
最後結果:同樣對一億的表插入更新20萬條資料,多次執行平均時間
sp_merge 14.48秒
sp_forall 6.63秒
sp_ins_up 44.33秒
從每5000條送出一次的時間可以得出來,forall最穩定,其次merge稍有起伏,手動執行插入更新浮動最大從0.3秒到6.9秒不等。
效率方面:forall優勢明顯,其次merge也不差,手動插入更新最慢且不穩定
代碼方面:merge into與手動插入更新 相對簡潔,forall就比較複雜
擴充性:手動插入更新可以加上許多業務性功能,forall方式目前oracle也提供了相當多的函數用于資料處理,是以次之,merge into就個人而言,相對笨重一些了。