Oracle數據表默認值列添加與行遷移(Row Migration)

在筆者之前的文章中,已經探討過給一個數據表添加有默認值列是一項非常危險的事情,特別是在在線生產環境下。給一張大數據表添加有默認值列,最直接的有下面幾個嚴重危害: 系統高負荷運行,消耗大量資源。添加列操作是一次性的DDL操作,生成大量的Redo Log記

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

系統高負荷運行,消耗大量資源。添加列操作是一次性的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的爆發。

2、Row 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
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已經推高了水位線HWM到12個塊,從空間分配也分配了新的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

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進行整理。我們說,在生產環境下,進行有默認值數據列的添加操作,會引起一系列的問題,要三思而行。

? 版權聲明
THE END
喜歡就支持一下吧
點贊11 分享