Oracle 11g大資料量表快速增加列
GPS平臺、網站建設、軟體開發、系統運維,找森大網路科技!
https://cnsendnet.taobao.com
來自森大科技官方部落格
http://www.cnsendblog.com/index.php/?p=1975
網址: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