1. 程式人生 > 其它 >Oracle 11g大資料量表快速增加列

Oracle 11g大資料量表快速增加列

GPS平臺、網站建設、軟體開發、系統運維,找森大網路科技!
https://cnsendnet.taobao.com
來自森大科技官方部落格
http://www.cnsendblog.com/index.php/?p=1975

Oracle 11g增加列,並帶預設值的新特性

網址:http://www.eygle.com/digest/2011/06/oracle_11g_default_column.html

永久連結:http://www.ixdba.com/html/y2007/m08/159-oracle11g-add-column.html


老和尚在他的blog中描述了了這個問題,我這裡做一個詳細的測試來說明這個問題。

在oracle 11g以前,如果需要在一個表中執行類似如下的命令,而且這個表本身已經有很多資料,那麼,這個操作將可能花費很長的時間,並且可能阻塞應用:

  • SQL>alter table table_name add field_name number default 0 not null;

因為這個操作需要修改以前所有的行,並把他們都修 改為預設值,如以上的0。但是,這個情況在oracle 11g中有了巨大的改變,oracle 11g中,如果對一個表增加一個列,並帶有預設值,Oracle並沒有真實的去修改以前的列,只不過通過查詢的時候,採用類似NVL(null,新值)的 方法轉換一下,讓以前的列看起來象有值一樣。

這個過程在select 的時候就轉換好了,所以,對於使用者看來,他們是有值的,這個細小的改動將大大減少這個語句的執行時間,使得這樣的語句在oracle 11g中不會引起任何阻塞,更不會影響效能了。

下面看一個簡單的對比說明:

首先是Oracle 10g的測試

  • Piner@10gR2>create table test(a int);
  • Table created.
  • Piner@10gR2>begin
  • 2 for i in 1..1000 loop
  • 3 insert into test values('1');
  • 4 end loop;
  • 5 commit;
  • 6 end;
  • 7 /
  • PL/SQL procedure successfully completed.
  • Piner@10gR2>set serveroutput on
  • Piner@10gR2>exec show_space('TEST');
  • Total Blocks............................8
  • Total Bytes.............................65536
  • Unused Blocks...........................0
  • Unused Bytes............................0
  • Last Used Ext FileId....................4
  • Last Used Ext BlockId...................135641
  • Last Used Block.........................8
  • PL/SQL procedure successfully completed.
  • Piner@10gR2>alter table test add flag char(2000) default 1 not null;
  • Table altered.
  • Piner@10gR2>exec show_space('TEST');
  • Total Blocks............................384
  • Total Bytes.............................3145728
  • Unused Blocks...........................0
  • Unused Bytes............................0
  • Last Used Ext FileId....................4
  • Last Used Ext BlockId...................134409
  • Last Used Block.........................128
  • PL/SQL procedure successfully completed.
  • Piner@10gR2>select count(*) from test where flag is null;
  • COUNT(*)
  • ----------
  • 0

可以看到的是,在增加預設值的列以後,該表的使用空間發生了巨大的變化,也證明資料庫修改了以前的所有的塊中的資料,讓他們都生效。

以下是Oracle 11g的操作

  • Piner@11gR1>create table test(a int);
  • Table created.
  • Piner@11gR1>begin
  • 2 for i in 1..1000 loop
  • 3 insert into test values('1');
  • 4 end loop;
  • 5 commit;
  • 6 end;
  • 7 /
  • PL/SQL procedure successfully completed.
  • Piner@11gR1>set serveroutput on
  • Piner@11gR1>exec show_space('TEST');
  • Total Blocks............................8
  • Total Bytes.............................65536
  • Unused Blocks...........................0
  • Unused Bytes............................0
  • Last Used Ext FileId....................4
  • Last Used Ext BlockId...................83745
  • Last Used Block.........................8
  • PL/SQL procedure successfully completed.
  • Piner@11gR1>alter table test add flag char(2000) default 1 not null;
  • Table altered.
  • Piner@11gR1>exec show_space('TEST');
  • Total Blocks............................8
  • Total Bytes.............................65536
  • Unused Blocks...........................0
  • Unused Bytes............................0
  • Last Used Ext FileId....................4
  • Last Used Ext BlockId...................83745
  • Last Used Block.........................8
  • PL/SQL procedure successfully completed.
  • Piner@11gR1>select count(*) from test where flag is null;
  • COUNT(*)
  • ----------
  • 0

這裡可以看到,同樣的記錄,做了同樣的操作,增加新列,並設定預設值以後,表的使用空間沒有發生任何變化,也同樣證明了Oracle沒有修改以前的塊中的資料。

這裡的例子只有1000條記錄,主要是為了體現了 空間的變化。如果是幾是萬條,或者幾百萬條以上的表,同樣的操作,oracle 11g是馬上就結束了,不會有任何影響,而oracle 10g可能需要非常長的時間,這個時間表是被鎖住的,不能執行任何DML操作,對於寫頻繁的應用來說,將會導致應用的阻塞。

新新增的列,如果指定了預設值,並沒有修改以前的塊,讀取以前的行的時候,可能發生一個轉換過程。但是,如果對於已經設定過預設值的情況,插入新值的時候,這麼預設值是怎麼處理的呢:

之前的情況:

  • Piner@11gR1> desc test;
  • Name Type Nullable Default Comments
  • ---- ---------- -------- ------- --------
  • A INTEGER Y
  • FLAG CHAR(2000) Y 0
  • Piner@11gR1>exec show_space('TEST');
  • Total Blocks............................8
  • Total Bytes.............................65536
  • Unused Blocks...........................0
  • Unused Bytes............................0
  • Last Used Ext FileId....................4
  • Last Used Ext BlockId...................83745
  • Last Used Block.........................8
  • PL/SQL procedure successfully completed.
  • Piner@11gR1>select count(*) from test;
  • COUNT(*)
  • ----------
  • 1000

插入新記錄:

  • Piner@11gR1>begin
  • 2 for i in 1..1000 loop
  • 3 insert into test(a) values('1');
  • 4 end loop;
  • 5 commit;
  • 6 end;
  • 7 /
  • PL/SQL procedure successfully completed.
  • Piner@11gR1>exec show_space('TEST');
  • Total Blocks............................384
  • Total Bytes.............................3145728
  • Unused Blocks...........................0
  • Unused Bytes............................0
  • Last Used Ext FileId....................4
  • Last Used Ext BlockId...................90889
  • Last Used Block.........................128
  • PL/SQL procedure successfully completed.

可以看到,新插入的1000條記錄,塊的個數發生了非常大的變化,證明了在新的記錄中,Oracle實際上是真正把預設值新增到塊中去了。但是,只有以前的記錄依然是沒有值的,需要靠轉換來完成。

現在有2000條記錄,是分2次插入進去的,這個時候,如果去掉預設值,會發生什麼情況?是一半有值,一半沒有值,還是都有值呢?我們再看:

  • Piner@11gR1>alter table test modify flag default null null;
  • Table altered.
  • Piner@11gR1>select count(*) from test where flag is null;
  • COUNT(*)
  • ----------
  • 0
  • Piner@11gR1>exec show_space('TEST');
  • Total Blocks............................384
  • Total Bytes.............................3145728
  • Unused Blocks...........................0
  • Unused Bytes............................0
  • Last Used Ext FileId....................4
  • Last Used Ext BlockId...................90889
  • Last Used Block.........................128
  • PL/SQL procedure successfully completed.

可以看到,雖然解除了預設值,塊的個數也沒有任何變化,以前的記錄依然是沒有真實的寫入任何數值到塊中的,但是,Oracle 依然能把以前的預設值顯示出來,因為is null查不出來任何記錄,而且查詢記錄就可以看到實際的值就是以前的預設值。

最後,再增加1000條記錄,這個時候可以發現,因為去掉了預設值,現在在另外一個列上,寫入的為null。

  • Piner@11gR1>begin
  • 2 for i in 1..1000 loop
  • 3 insert into test(a) values(1);
  • 4 end loop;
  • 5 commit;
  • 6 end;
  • 7 /
  • PL/SQL procedure successfully completed.
  • Piner@11gR1>exec show_space('TEST');
  • Total Blocks............................384
  • Total Bytes.............................3145728
  • Unused Blocks...........................0
  • Unused Bytes............................0
  • Last Used Ext FileId....................4
  • Last Used Ext BlockId...................90889
  • Last Used Block.........................128
  • PL/SQL procedure successfully completed.
  • Piner@11gR1>select count(*) from test where flag is null;
  • COUNT(*)
  • ----------
  • 1000

所以,對於整個過程分析下來,可以這麼認 為,oracle在add column default的時候,只是記錄了一個標記,標記為這個點之前的所有塊,如果是NULL的話,是需要轉換的,反之取出實際的值。而這個點之後的塊,就是按 照實際的值來處理,如果中途反覆修改,則以修改為準。

因為add column default,對於一個列,最多發生一次,所以,一個列只記錄一個參照點即可。

但是,對於這個特性,有人想問,如果想保持以前的值為null,不要強行轉換,但是,又想把新增加的列設定一個預設值,怎麼辦?其實也很簡單,跟以前一樣,分2步走即可,這樣的話,以前的值還是null,新的值將為預設值。

  • Piner@11gR1>alter table table_name add field_name number;
  • Piner@11gR1>alter table table_name modify field_name default 0;

-- Add/modify columns 資料量大的表新增欄位,需要加上not null,否則會很慢 edit by huch

alter table OBD_DATA add errorcode varchar2(20) default '' not null;

-- Add comments to the columns

comment on column OBD_DATA.errorcode

is '錯誤程式碼';

可以先這是為不可為空,後面再改成可為空,在湖北正式試了,執行後面那兩條sql語句也非常快

GPS平臺、網站建設、軟體開發、系統運維,找森大網路科技!
https://cnsendnet.taobao.com
來自森大科技官方部落格
http://www.cnsendblog.com/index.php/?p=1975