表列新增預設值的方法

to_be_Dba發表於2015-10-23
在修改表結構時,有時候會涉及到新增預設值。
下面是在11.2.0.3版本資料庫中進行測試的結果:

Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 
Connected as borpt
 
SQL> set timing on
SQL> select count(1) from user_mark_resultt;
 
  COUNT(1)
----------
   1451535
 
Executed in 0.219 seconds
當前資料量為145萬

操作方法1:先增加表列,再修改預設值
SQL> alter table user_mark_resultadd name2 varchar2(10) ;
 
Table altered
 
Executed in 0.047 seconds
 
SQL> alter table user_mark_result modify name2 default 'a';
 
Table altered
 
Executed in 0.063 seconds
 
操作方法2:直接增加表列並賦予預設值
SQL> alter table user_mark_result add name3 varchar2(10) default 'a' ;
 
Table altered
 
Executed in 45.86 seconds
 
結果如下:
SQL> select t.dealerid,t.name2,t.name3 from user_mark_result t where rownum<5;
 
DEALERID                       NAME2      NAME3
------------------------------ ---------- ----------
DL0120100619                              a
DL0120100621                              a
DL0120100628                              a
DL0120100904                              a
 
Executed in 0.062 seconds


通過以上比較可知:
1.為表增加欄位後,再修改預設值,已存在的資料不受影響
2.增加欄位語句中同時為欄位新增預設值,需要較長時間的鎖表,這在生產系統上通常是不可接受的


因此,在oracle中進行表的新增列預設值操作時,為減少鎖表時間,需要分三步操作:
1. 向表中新增欄位帶有default值時,做如下修改 
 alter table user_mark_result add name2 varchar2(10);

2.在欄位新增完成後,分多個小事務更新新加的欄位值,避免鎖表。可以建立錯誤日誌記錄表,記錄可能發生的錯誤:
create table err_log(status varchar2(200));

declare
  n1 number :=0;
  v_str varchar2(200);
begin
  for i in (select dealerid from user_mark_result)
   loop
    n1 := n1+1;
    update user_mark_result  set name2 = 'a' where dealerid = i.dealerid;
    if mod(n1,5000)=0 then 
      commit;    
    end if;
   end loop;
   commit;
exception
  when others then
    rollback;
    v_str :=  SQLCODE || '_' || SQLERRM;
    insert into err_log (status) values(v_str);
    commit;
end;
/

3.如果上述匿名塊正常執行,則刪除err_log表,完成操作;否則根據err_log中的錯誤記錄進行相應修改。

4.增加表列的預設值
 alter table user_mark_result modify name2 default 'a';





 
 另外,上面的更新方法步驟2需要在每天更新語句後進行判斷,如果使用遊標處理,每次更新5000條資料,則效率更高。
 以下是用order_table測試的結果:
 
 SQL> select count(1),to_char(e.order_time,'yyyy-mm-dd')
  2  from order_table e
  3  where e.order_time>=to_date('20150401','yyyymmdd')
  4  and  e.order_time<to_date('20150501','yyyymmdd')
  5  group by to_char(e.order_time,'yyyy-mm-dd')
  6  order by to_char(e.order_time,'yyyy-mm-dd');
 
  COUNT(1) TO_CHAR(E.ORDER_TIME,'YYYY-MM-
---------- ------------------------------
   1438047 2015-04-01
   1312835 2015-04-02
   1167460 2015-04-03
    921161 2015-04-04
    802476 2015-04-05
    750283 2015-04-06
    682537 2015-04-07
    651092 2015-04-08
    629104 2015-04-09
    667710 2015-04-10
    648531 2015-04-11
    658504 2015-04-12
    593864 2015-04-13
    576714 2015-04-14
    589528 2015-04-15
    612004 2015-04-16
    636615 2015-04-17
    631522 2015-04-18
    673595 2015-04-19
    649317 2015-04-20
    645253 2015-04-21
    635374 2015-04-22
    668124 2015-04-23
    658031 2015-04-24
    666737 2015-04-25
    729222 2015-04-26
    736643 2015-04-27
    740636 2015-04-28
    739578 2015-04-29
   1013999 2015-04-30
 
30 rows selected


通過以上查詢,4月5日~12日的資料量為4831733條,
4月22日~28日的資料量為4834767條,基本相同,作為測試資料。


1)建立錯誤記錄表:
SQL> drop table err_log;
 
Table dropped
 
Executed in 0.063 seconds
 
SQL> create table err_log(status varchar2(200));
 
Table created
 
Executed in 0.032 seconds
 
SQL> select * from err_log;
 
STATUS
--------------------------------------------------------------------------------
 
Executed in 0.016 seconds
 
2)使用遊標的方法進行資料更新: 
SQL> DECLARE
  2    TYPE typ_order_id IS TABLE OF order_table.order_id%type;
  3    order_id     typ_order_id;
  4    v_str varchar2(200);
  5    cursor cur_order is  --宣告遊標
  6      SELECT order_id
  7        FROM order_table e
  8       where e.order_time > to_date('20150405', 'yyyymmdd')
  9         and e.order_time < to_date('20150412', 'yyyymmdd');
 10  BEGIN
 11    open cur_order; --開啟遊標
 12    loop
 13      fetch cur_order BULK COLLECT  --將資料插入陣列,每5000條批量插入一次
 14        INTO order_id limit 5000;
 15      forall i in order_id.first .. order_id.last  --對這5000條資料進行批量更新
 16        update order_table e
 17           set e.new_price = 0
 18         where e.order_id = order_id(i);
 19      commit;
 20      exit when cur_order%notfound;
 21    end loop;
 22    close cur_order;  --迴圈結束後,關閉遊標
 23  exception
 24    when others then
 25      rollback;
 26      v_str :=  SQLCODE || '_' || SQLERRM;
 27      insert into err_log (status) values(v_str);
 28      commit;
 29  END;
 30  /
 
PL/SQL procedure successfully completed
 
Executed in 738.281 seconds
 


使用逐條更新,每5000條提交一次的方法:
SQL> declare
  2    n1 number :=0;
  3    v_str varchar2(200);
  4  begin
  5    for i in (SELECT order_id
  6        FROM order_table e
  7       where e.order_time > to_date('20150422', 'yyyymmdd')
  8         and e.order_time < to_date('20150429', 'yyyymmdd') )
  9     loop
 10      n1 := n1+1;
 11      update order_table  set new_price = 0 where order_id = i.order_id;
 12      if mod(n1,5000)=0 then
 13        commit;
 14      end if;
 15     end loop;
 16     commit;
 17  exception
 18    when others then
 19      rollback;
 20      v_str :=  SQLCODE || '_' || SQLERRM;
 21      insert into err_log (status) values(v_str);
 22      commit;
 23  end;
 24  /
 
PL/SQL procedure successfully completed
 
Executed in 901.36 seconds
 
SQL> select * from err_log;
 
STATUS
--------------------------------------------------------------------------------
 
Executed in 0.016 seconds
 
SQL> drop table err_log;
 
Table dropped
 
Executed in 0.094 seconds


使用遊標的方法比逐條插入的方法快了約160s,效能差距還是比較明顯的。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26451536/viewspace-1815860/,如需轉載,請註明出處,否則將追究法律責任。

相關文章