天天看點

oracle基于3種方法的大資料量插入更新

過程插入更新的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就個人而言,相對笨重一些了。