Oracle資料表預設值列新增與行遷移(Row Migration)

realkid4發表於2012-08-28

 

在筆者之前的文章中,已經探討過給一個資料表新增有預設值列是一項非常“危險”的事情,特別是在線上生產環境下。給一張大資料表新增有預設值列,最直接的有下面幾個嚴重危害:

 

ü  系統高負荷執行,消耗大量資源。新增列操作是一次性的DDL操作,生成大量的Redo Log記錄;

ü  長期資料表鎖定,阻礙生產系統作業。新增資料列期間,對資料表新增獨佔鎖,此時阻礙其他DML操作;

ü  破壞原有儲存結構,造成大量的行遷移(Row Migration)資料。在每個資料行嘗試新增進預設值,進行膨脹的同時,由於rowid的特性,會引起嚴重的行連結情況,損害原有資料表儲存結構;

 

本文主要想聊聊由於預設值新增帶來的行連結(Row Migration)現象。

 

1、從Row Migration現象談起

 

Row Migration本質上是一種由於Oracle儲存特性和資料行定位特性而發生的一種現象。在Oracle中,所有的資料行都是保留在資料塊單元上的。一個資料塊可以容納若干條資料(通常條件下)。一些資料列,如varchar2型別,大部分情況下都是根據輸入資料的長度進行空間分配。

 

那麼,如果資料行列填入了更大的資料,也就是空間發生了擴充。資料塊儲存上就會發生何種變化呢?每個資料塊都會預留一部分的空閒空間,作為資料行變化預留位置。如果長度繼續擴充,那麼會發生什麼呢?

 

Oracle會嘗試將這個資料行複製出,找個新的資料塊進行儲存。這樣,就可以放下資料塊。那麼,一個新的問題出現了,就是Rowid問題。

 

Oracle中,Rowid是定位一條記錄的實體地址。Rowid包括資料檔案相對編號、物件號、資料塊號和Slot行號。Rowid普遍作為資料行的標記,儲存在相關的索引葉子節點上。但是,當一個資料行被轉移儲存到另一個資料塊,本質上物理儲存位置已經發生變化。索引等物件中包括的Rowid面臨著失效的問題。

 

Oracle解決這個問題是透過“虛擬門牌”的方法。這個資料行位置雖然已經到另外的地方,但是對應的Rowid並沒有發生變化。當我們檢索資料,Server Process定位到原來的位置時,它會找到一個轉換跳轉地址,那裡面記錄著真正的Rowid地址。這個就是發生了Row Migration

 

Row Migration給系統效能帶來了很多潛在的問題。比如,一行資料原來只需要尋找一個資料塊記錄,現在就需要尋找多個資料塊才可以。這樣就是帶來的效能問題。

 

我們在進行預設值資料行新增的時候,就會帶來Row Migration的爆發。

 

2Row Migration與預設值列新增

 

下面我們透過實驗,來證明Row Migration的出現。我們選擇11gR2環境進行實驗。

 

 

SQL> select * from v$version;

 

BANNER

--------------------------------------------------------------------------------

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

PL/SQL Release 11.2.0.1.0 - Production

CORE        11.2.0.1.0         Production

 

SQL> create table t as select object_id from dba_objects where 1=0;

Table created

 

--新增若干條記錄;

SQL> insert into t select object_id from dba_objects where rownum<100;

99 rows inserted

 

SQL> commit;

Commit complete

 

 

資料表T,在儲存結構和空間分配上情況如下:

 

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

SQL> select bytes, blocks,extents from user_segments where segment_name='T';

 

     BYTES     BLOCKS    EXTENTS

---------- ---------- ----------

     65536          8          1

 

SQL> select blocks from user_tables where table_name='T';

 

    BLOCKS

----------

         1

 

 

User_segment中記錄著給資料段分配的總空間,但這並不代表全部的HWM位置。User_tables中的blocks,才代表HWM下資料塊的個數。從上面的結果看,HWM下一共只有一個資料塊。從rowid分析看,實際也的確如此。

 

 

SQL> select dbms_rowid.rowid_block_number(rowid) blockno, count(*) from t group by dbms_rowid.rowid_block_number(rowid);

 

   BLOCKNO   COUNT(*)

---------- ----------

     85857         99

 

 

下面我們進行資料列新增。

 

 

SQL> alter table t add vc varchar2(1000) default lpad('T',500,'T');

Table altered

 

Executed in 0.078 seconds

 

 

對應的空間使用情況如下:

 

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

Executed in 0.141 seconds

 

SQL> select blocks from user_tables where table_name='T';

 

    BLOCKS

----------

        12

 

Executed in 0.016 seconds

 

 

SQL> select bytes, blocks,extents from user_segments where segment_name='T';

 

     BYTES     BLOCKS    EXTENTS

---------- ---------- ----------

    131072         16          2

 

SQL> select dbms_rowid.rowid_block_number(rowid) blockno, count(*) from t group by dbms_rowid.rowid_block_number(rowid);

 

   BLOCKNO   COUNT(*)

---------- ----------

     85857         99

 

Executed in 0.016 seconds

 

 

上面的情況可以看出,Oracle的資料表T已經推高了水位線HWM12個塊,從空間分配也分配了新的extent使用。

 

但是,所有資料行rowid沒有變化。所有資料行的“門牌號”都沒有變化,但是儲存呢?很詭異的增加了。正常容量下,資料塊情況應該是如下:

 

 

SQL> create table t_bak as select * from t;

 

Table created

 

SQL> exec dbms_stats.gather_table_stats(user,'T_BAK',cascade => true);

 

PL/SQL procedure successfully completed

 

SQL> select bytes, blocks,extents from user_segments where segment_name='T_BAK';

 

     BYTES     BLOCKS    EXTENTS

---------- ---------- ----------

    131072         16          2

 

SQL> select blocks from user_tables where table_name='T_BAK';

 

    BLOCKS

----------

         8

 

SQL> select dbms_rowid.rowid_block_number(rowid) blockno, count(*) from t_bak group by dbms_rowid.rowid_block_number(rowid);

 

   BLOCKNO   COUNT(*)

---------- ----------

     86589         14

     86588         14

     86585         14

     86586         14

     86591         14

     86590         14

     86587         14

     86592          1

 

8 rows selected

 

 

下面,我們來證明發生了行連結情況。

 

3、資料錶行連結檢驗

 

Analyze語句一度是非常流行的收集資料表統計量的操作方式。但是隨著dbms_stats包的成熟推廣,analyze在統計量收集方面的功能已經漸漸弱化。但是,Oracle依然保留了這個語句的兩個基本功能:對資料表進行行連結(Row Migration)檢測和索引健康程度檢測。

 

下面使用analyze語句進行資料表T的檢測。首先我們需要建立分析結果的容納資料表。

 

--呼叫ORACLE_HOME下的指令碼;

SQL> @?/rdbms/admin/utlchain.sql

 

Table created.

 

SQL> desc chained_rows;

 Name                                      Null?    Type

 ----------------------------------------- -------- ----------------------------

 OWNER_NAME                                         VARCHAR2(30)

 TABLE_NAME                                         VARCHAR2(30)

 CLUSTER_NAME                                       VARCHAR2(30)

 PARTITION_NAME                                     VARCHAR2(30)

 SUBPARTITION_NAME                                  VARCHAR2(30)

 HEAD_ROWID                                         ROWID

 ANALYZE_TIMESTAMP                                  DATE

 

SQL> create public synonym chained_rows for chained_rows;

 

Synonym created.

 

SQL> grant all on chained_rows to public;

 

Grant succeeded.

 

 

分析資料表,如下:

 

--檢驗資料行Row Migration情況;

SQL> analyze table t list chained rows into chained_rows;

Table analyzed

 

Executed in 0.125 seconds

 

--發生Row Migration次數;

SQL> select count(*) from chained_rows;

 

  COUNT(*)

----------

        86

 

Executed in 0.016 seconds

 

SQL> select head_rowid from chained_rows where rownum<5;

 

HEAD_ROWID

------------------

AAASUCAABAAAU9hAAN

AAASUCAABAAAU9hAAO

AAASUCAABAAAU9hAAP

AAASUCAABAAAU9hAAQ

 

Executed in 0.016 seconds

 

SQL> select * from t where rowid='AAASUCAABAAAU9hAAQ';

 

 OBJECT_ID VC

---------- --------------------------------------------------------------------------------

        38 TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT

 

Executed in 0.016 seconds

 

 

99行記錄中,發生了86次行連結Row Migration情況。

 

 

4、結論

 

解決Oracle Row Migration的方法,就是進行資料表重構,重新對儲存結構和Rowid進行整理。我們說,在生產環境下,進行有預設值資料列的新增操作,會引起一系列的問題,要三思而行。

 

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

相關文章