亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 數據庫 > Oracle > 正文

關于Oracle數據庫中行遷移/行鏈接的問題

2024-08-29 13:51:45
字體:
來源:轉載
供稿:網友
一、行遷移/行鏈接的介紹

在實際的工作中我們經常會碰到一些Oracle數據庫性能較低的問題,當然,引起Oracle數據庫性能較低的原因是多方面的,我們能夠通過一些正確的設計和診斷來盡量的避免一些Oracle數據庫性能不好,Row Migration (行遷移) & Row Chaining (行鏈接)就是其中我們可以盡量避免的引起Oracle數據庫性能低下的潛在問題。通過合理的診斷行遷移/行鏈接,我們可以較大幅度上提高Oracle數據庫的性能。

那究竟什么是行遷移/行鏈接呢,先讓我們從Oracle的block開始談起。

操作系統的最小讀寫操作單元是操作系統的block,所以當創建一個Oracle數據庫的時候我們應該講數據庫的block size設置成為操作系統的block size的整數倍,Oracle block是Oracle數據庫中讀寫操作的最小單元,Oracle9i之前的Oracle數據庫版本中Oracle block一旦在創建數據庫的時候被設定后就沒法再更改。為了在創建數據庫之前確定一個合理的Oracle block的大小,我們需要考慮一些因素,例如數據庫本身的大小以及并發事務的數量等。使用一個合適的Oracle block大小對于數據庫的調優是非常重要的。Oracle block的結構如下圖所示:
   圖一:Oracle Block結構圖

由上圖我們可以看出,一個Oracle block由三個部分組成,分別是數據塊頭、自由空間、實際數據三部份組成。

數據塊頭:主要包含有數據塊地址的一些基本信息和段的類型,以及表和包含有數據的實際行的地址。

自由空間:是指可以為以后的更新和插入操作分配的空間,大小由PCTFREE和PCTUSED兩個參數影響。

實際數據:是指在行內存儲的實際數據。

  當創建或者更改任何表和索引的時候,Oracle在空間控制方面使用兩個存儲參數:

  PCTFREE:為將來更新已經存在的數據預留空間的百分比。

  PCTUSED:用于為插入一新行數據的最小空間的百分比。這個值決定了塊的可用狀態??捎玫膲K時可以執行插入的塊,不可用狀態的塊只能執行刪除和修改,可用狀態的塊被放在freelist中。

  當表中一行的數據不能在一個數據block中放入的時候,這個時候就會發生兩種情況,一種是行鏈接,另外一種就是行遷移了。

  行鏈接產生在第一次插入數據的時候假如一個block不能存放一行記錄的情況下。這種情況下,Oracle將使用鏈接一個或者多個在這個段中保留的block存儲這一行記錄,行鏈接比較輕易發生在比較大的行上,例如行上有LONG、LONG RAW、LOB等數據類型的字段,這種時候行鏈接是不可避免的會產生的。

  當一行記錄初始插入的時候事可以存儲在一個block中的,由于更新操作導致行長增加了,而block的自由空間已經完全滿了,這個時候就產生了行遷移。在這種情況下,Oracle將會遷移整行數據到一個新的block中(假設一個block中可以存儲下整行數據),Oracle會保留被遷移行的原始指針指向新的存放行數據的block,這就意味著被遷移行的ROW ID是不會改變的。

  當發生了行遷移或者行鏈接,對這行數據操作的性能就會降低,因為Oracle必須要掃描更多的block來獲得這行的信息。

  下面舉例來具體說明行遷移/行鏈接的產生過程。

  先創建一個pctfree為20和pctused為50的測試表:

  create table test(

  col1 char(20),

  col2 number)

  storage (

  pctfree 20

  pctused 50);

  當插入一條記錄的時候,Oracle會在free list中先去尋找一個自由的塊,并且將數據插入到這個自由塊中。而在free list中存在的自由的塊是由pctfree值決定的。初始的空塊都是在free list中的,直到塊中的自由空間達到pctfree的值,此塊就會從free list中移走,而當此塊中的使用空間低于pctused的時候,此塊又被重新放到free list中。

  Oracle使用free list機制可以大大的提高性能,對于每次的插入操作,Oracle只需要查找free list就可以了,而不是去查找所有的block來尋找自由空間。

  假設第一次插入數據使用的一個空的block,如下圖所示:



圖二:Oracle空的block結構圖

假設插入第一條記錄的時候占用一個block的10%的空間(除去block頭占去的大?。S嗟淖杂煽臻g90%大于pctfree20%,因此這個block還將繼續為下次的插入操作提供空間。



圖三:插入10%后的Oracle block結構圖

再連續插入七條記錄,使block的剩余自由空間剩下20%,此時,這個block將要從free list中移走,假如再插入記錄,Oracle將再free list中尋找下一個空余的block去存放后來插入的數據。



圖四:插入80%后的Oracle block結構圖

  此時假如去更新第一條插入的記錄,使其行長增加15%,Oracle將會使用這個block中剩余的20%的自由空間來存放此行數據,假如再更新第二條記錄,同樣的使其行長增加15%,而此block中只剩下5%的自由空間,不夠存放更新的第二條記錄,于是Oracle會在free list中尋找一個有自由空間(10%+15%)的block來存放這行記錄的block去存儲,在原來的block中保存了指向新的block的指針,原來這行記錄的ROW ID保持不變,這個時候就產生了行遷移。


  而當我們插入一條新紀錄的時候,假如一個blcok不足以存放下這條記錄,Oracle就會尋找一定數量的block一起來容納這條新的記錄,這個時候就產生了行鏈接,行鏈接主要產生在LOB、CLOB、BLOB和大的VA行鏈接HAR2數據類型上。

  具體我們通過下面的一個試驗來查看行鏈接和行遷移是如何產生并在數據文件中體現出來的。



 


先查看ALLAN這個表空間的數據文件號,為了便于測試,我只建立了一個數據文件。

SQL> select file_id from dba_data_files where tablespace_name='ALLAN';

   FILE_ID

----------

        23

創建一個測試表test:

SQL> create table test ( x int PRimary key, a char(2000), b char(2000), c char(2000), d char(2000), e char(2000) ) tablespace allan;

Table created.

因為我的數據庫的db_block_size是8K,所以我創建的表有五個字段,每個占2000個字節,這樣一行記錄大約10K,就能超過一個block的大小了。

然后插入一行記錄,只有一個字段的:

SQL> insert into test(x) values (1);

1 row created.

SQL> commit;

Commit complete.

查找這行記錄所在的block,并dump出來:

SQL> select dbms_rowid.rowid_block_number(rowid) from test;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)

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

                                  34

SQL> alter system dump datafile 23 block 34;

System altered.

在udump目錄下查看trace文件的內容如下:

Start dump data blocks tsn: 34 file#: 23 minblk 34 maxblk 34

buffer tsn: 34 rdba: 0x05c00022 (23/34)

scn: 0x0000.013943f3 seq: 0x01 flg: 0x02 tail: 0x43f30601

frmt: 0x02 chkval: 0x0000 type: 0x06=trans data

Block header dump:  0x05c00022

 Object id on Block? Y

 seg/obj: 0x3ccd  csc: 0x00.13943ef  itc: 2  flg: O  typ: 1 - DATA

     fsl: 0  fnx: 0x0 ver: 0x01

Itl           Xid                  Uba         Flag  Lck        Scn/Fsc

0x01   0x000a.02e.00000ad7  0x00800036.03de.18  --U-    1  fsc 0x0000.013943f3

0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000

data_block_dump,data header at 0xadb505c

===============

tsiz: 0x1fa0

hsiz: 0x14

pbl: 0x0adb505c

bdba: 0x05c00022

     76543210

flag=--------

ntab=1

nrow=1

frre=-1

fsbo=0x14

fSEO=0x1f9a

avsp=0x1f83

tosp=0x1f83

0xe:pti[0]      nrow=1  offs=0

0x12:pri[0]     offs=0x1f9a

block_row_dump:

tab 0, row 0, @0x1f9a

tl: 6 fb: --H-FL-- lb: 0x1  cc: 1

col  0: [ 2]  c1 02

end_of_block_dump


End dump data blocks tsn: 34 file#: 23 minblk 34 maxblk 34

對其中的一些信息做一些解釋:

Fb:H是指行記錄的頭,L是指行記錄的最后一列,F是指行記錄的第一列。

Cc:列的數量

Nrid:對于行鏈接或者行遷移來說的下一個row id的值

由上面的dump信息我們可以看出來當前表test是沒有行鏈接或者行遷移的。

然后更新test表,并重新dump出來:

SQL> update test set a='test',b='test',c='test',d='test',e='test' where x=1;

1 row updated.

SQL> commit;

Commit complete.

此時應該有行遷移/行鏈接產生了。

SQL> alter system dump datafile 23 block 34;

System altered.

在udump目錄下查看trace文件的內容如下:

Start dump data blocks tsn: 34 file#: 23 minblk 34 maxblk 34

buffer tsn: 34 rdba: 0x05c00022 (23/34)

scn: 0x0000.0139442b seq: 0x01 flg: 0x02 tail: 0x442b0601

frmt: 0x02 chkval: 0x0000 type: 0x06=trans data

Block header dump:  0x05c00022

 Object id on Block? Y

 seg/obj: 0x3ccd  csc: 0x00.1394429  itc: 2  flg: -  typ: 1 - DATA

     fsl: 0  fnx: 0x0 ver: 0x01

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc

0x01   0x000a.02e.00000ad7  0x00800036.03de.18  C---    0  scn 0x0000.013943f3

0x02   0x0004.002.00000ae0  0x0080003b.0441.11  --U-    1  fsc 0x0000.0139442b

data_block_dump,data header at 0xadb505c

===============

tsiz: 0x1fa0

hsiz: 0x14

pbl: 0x0adb505c

bdba: 0x05c00022

     76543210

flag=--------

ntab=1

nrow=1

frre=-1

fsbo=0x14

fseo=0x178a

avsp=0x177c

tosp=0x177c

0xe:pti[0]      nrow=1  offs=0

0x12:pri[0]     offs=0x178a

block_row_dump:

tab 0, row 0, @0x178a

tl: 2064 fb: --H-F--N lb: 0x2  cc: 3

nrid:  0x05c00023.0

col  0: [ 2]  c1 02

col  1: [2000]

 74 65 73 74 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20

 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20

 …………

col  2: [48]

 74 65 73 74 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20

 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20

end_of_block_dump

End dump data blocks tsn: 34 file#: 23 minblk 34 maxblk 34

我們不難看出,nrid出現了值,指向了下一個row id,證實剛剛的update操作使這行記錄產生了行鏈接或者行遷移了。


 

二、行遷移/行鏈接的檢測

  通過前面的介紹我們知道,行鏈接主要是由于數據庫的db_block_size不夠大,對于一些大的字段沒法在一個block中存儲下,從而產生了行鏈接。對于行鏈接我們除了增大db_block_size之外沒有別的任何辦法去避免,但是因為數據庫建立后db_block_size是不可改變的(在9i之前),對于Oracle9i的數據庫我們可以對不同的表空間指定不同的db_block_size,因此行鏈接的產生幾乎是不可避免的,也沒有太多可以調整的地方。行遷移則主要是由于更新表的時候,由于表的pctfree參數設置太小,導致block中沒有足夠的空間去容納更新后的記錄,從而產生了行遷移。對于行遷移來說就非常有調整的必要了,因為這個是可以調整和控制清除的。


  如何檢測數據庫中存在有了行遷移和行鏈接呢?我們可以利用Oracle數據庫自身提供的腳本utlchain.sql(在$ORACLE_HOME/rdbms/admin目錄下)生成chained_rows表,然后利用ANALYZE TABLE table_name LIST CHAINED ROWS INTO chained_rows命令逐個分析表,將分析的結果存入chained_rows表中。從utlchain.sql腳本中我們看到chained_rows的建表腳本,對于分區表,cluster表都是適用的。然后可以使用拼湊語句的辦法生成分析所需要的表的腳本,并執行腳本將具體的分析數據放入Chained_rows表中,例如下面是分析一個用戶下所有表的腳本:


SPOOL list_migation_rows.sql

SET ECHO OFF

SET HEADING OFF

SELECT 'ANALYZE TABLE ' table_name ' LIST CHAINED ROWS INTO chained_rows;' FROM user_tables;

SPOOL OFF


然后查詢chained_rows表,可以具體查看某張表上有多少的行鏈接和行遷移。


SELECT table_name, count(*) from chained_rows GROUP BY table_name;


當然,也可以查詢v$sysstat視圖中的’table fetch continued row’列得到當前的行鏈接和行遷移數量。


SELECT name, value FROM v$sysstat WHERE name = 'table fetch continued row';


可以使用如下的腳本來直接查找存在有行鏈接和行遷移的表,自動完成所有的分析和統計。


accept owner prompt " Enter the schema name to check for Row Chaining (RETURN for All): "
prompt
prompt
accept table prompt " Enter the table name to check (RETURN for All tables owned by &owner): "
prompt
prompt
set head off serverout on term on feed off veri off echo off
!clear
prompt
declare
v_owner varchar2(30);
v_table varchar2(30);
v_chains number;
v_rows number;
v_count number := 0;
sql_stmt varchar2(100);
dynamicCursor INTEGER;
dummy INTEGER;
cursor chains is
select count(*) from chained_rows;
cursor analyze is
select owner, table_name
from sys.dba_tables
where owner like upper('%&owner%')
and table_name like upper('%&table%')
order by table_name;
begin
dbms_output.enable(64000);
open analyze;
fetch analyze into v_owner, v_table;
while analyze%FOUND loop
dynamicCursor := dbms_sql.open_cursor;
sql_stmt := 'analyze table 'v_owner'.'v_table' list chained rows into chained_rows';
dbms_sql.parse(dynamicCursor, sql_stmt, dbms_sql.native);
dummy := dbms_sql.execute(dynamicCursor);
dbms_sql.close_cursor(dynamicCursor);
open chains;
fetch chains into v_chains;
if (v_chains != 0) then
if (v_count = 0) then
dbms_output.put_line(CHR(9)CHR(9)CHR(9)'<<<<< Chained Rows Found >>>>>');
v_count := 1;
end if;
dynamicCursor := dbms_sql.open_cursor;
sql_stmt := 'Select count(*) v_rows'' From 'v_owner'.'v_table;
dbms_sql.parse(dynamicCursor, sql_stmt, dbms_sql.native);
dbms_sql.DEFINE_COLUMN(dynamicCursor, 1, v_rows);
dummy := dbms_sql.execute(dynamicCursor);
dummy := dbms_sql.fetch_rows(dynamicCursor);
dbms_sql.COLUMN_VALUE(dynamicCursor, 1, v_rows);
dbms_sql.close_cursor(dynamicCursor);
dbms_output.put_line(v_owner'.'v_table);
dbms_output.put_line(CHR(9)'---> Has 'v_chains' Chained Rows and 'v_rows' Num_Rows in it!');
dynamicCursor := dbms_sql.open_cursor;
sql_stmt := 'truncate table chained_rows';
dbms_sql.parse(dynamicCursor, sql_stmt, dbms_sql.native);
dummy := dbms_sql.execute(dynamicCursor);

dbms_sql.close_cursor(dynamicCursor);
v_chains := 0;
end if;
close chains;
fetch analyze into v_owner, v_table;
end loop;
if (v_count = 0) then
dbms_output.put_line('No Chained Rows found in the 'v_owner' owned Tables!');
end if;
close analyze;
end;
/
set feed on head on
prompt


 

三、行遷移和行鏈接的清除

由于對于行鏈接來說只能增大db_block_size來清除,而db_block_size在創建了數據庫后又是不能改變了的,所以這里對行鏈接的清除不做過多的敘述了,主要是針對行遷移來談談在實際的生產系統中如何去清除。

對于行遷移的清除,一般來說分為兩個步驟:第一步,控制住行遷移的增長,使其不在增多;第二步,清除掉以前存在的行遷移。

眾所周知,行遷移產生的主要原因是因為表上的pctfree參數設置過小導致的,而要實現第一步控制住行遷移的增長,就必須設置好一個正確合適的pctfree參數,否則即使清除了當前的行遷移后馬上又會產生很多新的行遷移。當然,這個參數也不是越大越好的,假如pctfree設置的過大,會導致數據塊的利用率低,造成空間的大量浪費,因此必須設置一個合理的pctfree參數。如何去確定一個表上合理的pctfree參數呢,一般來說有兩種方法。

第一種是定量的的設定方法,就是利用公式來設定pctfree的大小。先使用ANALYZE TABLE table_name ESTIMATE  STATISTICS命令來分析要修改pctfree的表,然后查看user_tables中的AVG_ROW_LEN列值,得到一個平均行長AVG_ROW_LEN1,然后大量的對表操作之后,再次使用上述命令分析表,得到第二個平均行長AVG_ROW_LEN2,然后運用公式100 * (AVG_ROW_LEN2-AVG_ROW_LEN1)/(AVG_ROW_LEN2-AVG_ROW_LEN1 + 原始的AVG_ROW_LEN)得出的結果就是定量計算出來的一個合適的pctfree的值。這種方法因為是定量計算出來的,可能不一定會很準確,而且因為要分析表,所以對于使用RBO執行計劃的系統不是很適用。例如:avg_row_len_1 = 60,avg_row_len_2 = 70,則平均修改量為 10,PCTFREE 應調整為 100 * 10 /(10 + 60)= 16.7% 。

第二種是差分微調的方法,先查詢到當前表的pctfree的值,然后監控和調整pctfree參數,每次增加一點pctfree的大小,每次增加的比例不要超過5個百分點,然后使用ANALYZE TABLE TABLE_NAME LIST CHAINED ROWS INTO chained_rows命令分析每次所有的行遷移和行鏈接的增長情況,對于不同的表采取不同的增長比例,對于行遷移增長的比較快的表pctfree值就增加的多點,對于增長慢的表就增加的少點,直到表的行遷移基本保持不增長了為止。但是注重不要把pctfree調的過大,一般在40%以下就可以了,否則會造成空間的很大浪費和增加數據庫訪問的IO。

使用上述的方法控制住了當前表的行遷移的增長之后,就可以開始清除之前表上存在的行遷移了。是否清除掉行遷移,關系到系統的性能是否能夠有很大的提高。因此,對于以前存在的行遷移是一定而且必須要清除掉的。清除掉已經存在的行遷移有很多方法,但是并不是所有的方法都能適用所有的情況,例如表中的記錄數多少,表上的關聯多少、表上行遷移的數量多少等等這些因素都會是成為制約你使用什么方法清除的條件,因此,根據表的特點和具體情況的不同我們應該采用不同的方法去清除行遷移。下面我將逐一介紹各種清除行遷移的方法以及它們各自適用的不同情況。

方法一:傳統的清除行遷移的方法

具體步驟如下:

1.  執行$ORACLE_HOME/rdbms/admin目錄下的utlchain.sql腳本創建chained_rows表。


@$ORACLE_HOME/rdbms/admin/utlchain.sql


2.  將存在有行遷移的表(用table_name代替)中的產生行遷移的行的rowid放入到chained_rows表中。  


ANALYZE TABLE table_name LIST CHAINED ROWS INTO chained_rows;


3.  將表中的行遷移的row id放入臨時表中保存。


CREATE  TABLE  table_name_temp  AS

SELECT  *  FROM  table_name

WHERE  rowid  IN

(SELECT  head_rowid  FROM  chained_rows

WHERE  table_name  =  'table_name');


4.  刪除原來表中存在的行遷移的記錄行。


DELETE  table_name

WHERE  rowid  IN

(SELECT  head_rowid

FROM  chained_rows

WHERE  table_name  =  'table_name');


5.  從臨時表中取出并重新插入那些被刪除了的數據到原來的表中,并刪除臨時表。


INSERT  INTO  table_name  SELECT  *  FROM  table_name_temp;

DROP  TABLE  table_name_temp;


對于這種傳統的清除RM的方法,優點是執行起來過程比較簡單,輕易實現。但是這種算法的缺陷是沒有考慮到表關聯的情況,在大多數數據庫中很多表都是和別的表之間有表關聯的,有外鍵的限制,這樣就造成在步驟3中根本無法delete掉存在有行遷移的記錄行,所以這種方法能夠適用的表的范圍是有限的,只能適用于表上無任何外鍵關聯的表。由于這種方法在插入和刪除數據的時候都沒有disable掉索引,這樣導致主要消耗時間是在刪除和插入時維持索引樹的均衡上了,這個對于假如記錄數不多的情況時間上還比較短,但是假如對于記錄數很多的表這個所消耗的時間就不是能夠接受的了。顯然,這種方法在處理大數據量的表的時候顯然是不可取的。


以下是一個具體在生產數據庫上清除行遷移的例子,在這之前已經調整過表的pctfree參數至一個合適的值了:


SQL>@$ORACLE_HOME/rdbms/admin/utlchain.sql

Table created.

SQL> ANALYZE TABLE CUSTOMER LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL>SELECT count(*) from chained_rows;

TABLE_NAME                       COUNT(*)

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

CUSTOMER                            21306

1 rows selected.

查看在CUSTOMER表上存在的限制:

SQL>select CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where TABLE_NAME='CUSTOMER';

CONSTRAINT_NAME                C TABLE_NAME

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

PK_CUSTOMER1                   P CUSTOMER

SQL>select CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where R_CONSTRAINT_NAME='PK_CUSTOMER1';

no rows selected

SQL> CREATE  TABLE  CUSTOMER_temp  AS

SELECT  *  FROM  CUSTOMER   WHERE  rowid  IN

(SELECT  head_rowid  FROM  chained_rows

WHERE  table_name  =  'CUSTOMER');

Table created.

SQL>select count(*) from CUSTOMER;

  COUNT(*)

----------

    338299

SQL> DELETE  CUSTOMER WHERE rowid  IN

(SELECT  head_rowid

FROM  chained_rows

WHERE  table_name  =  'CUSTOMER');

21306 rows deleted.

SQL> INSERT  INTO  CUSTOMER SELECT  *  FROM  CUSTOMER_temp;

21306 rows created.

SQL> DROP  TABLE  CUSTOMER_temp;

Table dropped.

SQL> commit;

Commit complete.

SQL> select count(*) from CUSTOMER;

  COUNT(*)

----------

338299

SQL> truncate table chained_rows;

Table truncated.

SQL> ANALYZE TABLE CUSTOMER LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL> select count(*) from chained_rows;

  COUNT(*)

----------

         0


以上整個清除兩萬多行的行遷移過程在三分鐘左右,而且全部都在聯機的狀態下完成,基本上不會對業務有什么影響,唯一就是在要清除行遷移的表上不能有對外鍵的限制,否則就不能采用這個方法去清除了。

 

  方法二:改進了的傳統清除行遷移的方法

1.  執行$ORACLE_HOME/rdbms/admin目錄下的utlchain.sql腳本創建chained_rows表。

2.  禁用所有其它表上關聯到此表上的所有限制。

3.  將表中的行遷移的row id放入臨時表中保存。

4.  刪除原來表中存在的行遷移的記錄行。

5.  從臨時表中取出并重新插入那些被刪除了的數據到原來的表中,并刪除臨時表。

6.  啟用所有其它表上關聯到此表上的所有限制。


這種算法是對傳統算法的一種改進,對于使用這種算法來清除行遷移,考慮到了表之間的關聯,還可以靈活的利用的TOAD工具生成的表關聯信息,是一種比較適合于清除行遷移的一種方法。但是因為使用這種方法后來需要重建索引,假如記錄數很大,比如說上千萬條以上的記錄的表,就不是很合適了,因為這個重建索引的時間會很長,是線性時間復雜度的,而重建索引是會導致索引所在的表被鎖定的,從而導致插入不了新的記錄,重建索引的時間太長導致記錄長時間插入不了是會嚴重影響應用的,甚至導致數據的丟失,因此這個是使用這個方法前必須要考慮到的一個重要因素;對于8i以上的版本可以使用online的方法來重建索引,這樣不會導致鎖表,但是會有額外更多的開銷,時間會很長。再者,因為這種方法在插入記錄和刪除記錄都是帶著索引的,假如表上的行遷移比較多,這樣耗時間會比較長,而且占用資源也會比較大,因此只適用于表上行遷移存在的比較少的表??偟膩碚f,這種方法對于表記錄太多或者是表上的行遷移太多的情況都不是很適用,比較適合表記錄少和表上行遷移都不太多的情況。

以下是一個具體在生產數據庫上清除行遷移的例子,在這之前已經調整過表的pctfree參數至一個合適的值了:


SQL>select index_name,index_type,table_name from user_indexes where table_name='TERMINAL';

INDEX_NAME                     INDEX_TYPE         TABLE_NAME

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

INDEX_TERMINAL_TERMINALCODE    NORMAL              TERMINAL

I_TERMINAL_ID_TYPE             NORMAL              TERMINAL

I_TERMINAL_OT_OID              NORMAL              TERMINAL

PK_TERMINAL_ID                 NORMAL              TERMINAL

UI_TERMINAL_GOODIS_SSN         NORMAL              TERMINAL

SQL>select  CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where R_CONSTRAINT_NAME='PK_TERMINAL_ID';

CONSTRAINT_NAME                C TABLE_NAME

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

SYS_C003200                    R CONN

SQL>alter table CONN disable constraint SYS_C003200;

Table altered.

SQL>CREATE  TABLE  TERMINAL_temp  AS

SELECT  *  FROM  TERMINAL

WHERE  rowid  IN

(SELECT  head_rowid  FROM  chained_rows

WHERE  table_name  =  'TERMINAL'); 

Table created.

SQL>select count(*) from TERMINAL_temp;

  COUNT(*)

----------

      8302

SQL>DELETE  TERMINAL

WHERE  rowid  IN

(SELECT  head_rowid

FROM  chained_rows

WHERE  table_name  =  'TERMINAL');


 8302 rows deleted.

SQL>INSERT  INTO  TERMINAL SELECT  *  FROM  TERMINAL_temp;

8302 rows created.

SQL>alter table CONN disable constraint SYS_C003200;

Table altered.

SQL>select count(*) from terminal;

  COUNT(*)

----------

647799

SQL>truncate table chained_rows;

Table truncated.

SQL>ANALYZE TABLE TERMINAL LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL>select count(*) from chained_rows;

  COUNT(*)

----------

         0


從上面過程中可以看出,對TERMINAL這張表的行遷移清除耗時總共不到五分鐘的時間,總體來說還是比較快的。從我在生產數據庫中清除行遷移的經驗來說,這種方法基本適用于大部分存在有行遷移的表。

 

  方法三:使用TOAD工具清除行遷移的方法

1.  備份要清除RM的表。


RENAME table_name TO table_name_temp;


2.  Drop 所有其它表上關聯到table_name的外鍵限制。


SELECT CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where R_CONSTRAINT_NAME in (SELECT CONSTRAINT_NAME from USER_CONSTRAINTS where TABLE_NAME='table_name' AND CONSTRAINT_TYPE=’P’);

ALTER TABLE table_name DROP CONSTRAINT XXXX;(XXXX為上述的查詢結果)


3.  重建1中被rename的表。


CREATE TABLE table_name AS SELECT * FROM table_name_temp WHERE 0 = 1;


4.  重建表中原來的數據。


INSERT /*+ APPEND */ INTO table_name SELECT * FROM table_name_temp;


5.  刪除在table_name_temp上的索引和關聯其他表的外鍵。

6.  在table_name上建立和原來一樣的索引、主鍵和所有的外鍵限制。

7.  重新編譯相關的存儲過程、函數和包。

8.  刪除表table_name_temp。

對于使用這種方法來清除行遷移,全部的代碼都是可以由TOAD工具來生成的。由于此方法把表上的關聯考慮進去了,也是一種比較的全面的考慮的一種清除方法,而且在清除過程中重建了表和索引,對于數據庫的存儲和性能上都有提高。因為這種方法一開始是rename表為臨時表,然后重建一個新表出來的,因此需要兩倍的表的空間,因此在操作之前一定要檢查要清除的表所在的表空間的free空間是否足夠;但是也有一定的缺陷,因為在新表中重新插入原來的數據后需要重建索引和限制,因此在時間和磁盤的空間上都有比較大的開銷,而且對于前臺的應用可能會有一段時間的中斷,當然,這個中斷時間就主要是消耗在重建索引和重建限制上了,而時間的長短跟需要重建索引和限制的多少以及表的記錄多少等等因素都有關系。使用這種方法對于7*24小時要求的系統上清除行遷移不是很合適,因為使用這種方法會導致系統可能有一段時間的停機,假如系統的實時性比較高,這種方法就不是很適用了。

 

  方法四:使用EXP/IMP工具清除行遷移的方法

1.    使用EXP導出存在有行遷移的表。

2.    然后TRUNCATE原來的表。

3.    IMP開始導出的表。

4.    重建表上所有的索引。(可選)

使用這種方法可以不用重建索引,省去了這部分時間,但是完成之后索引的使用效率不會很高,最好是在以后逐步的在線重建索引,這樣是可以不需要中斷業務的。但是需要考慮的是IMP的時候會比較慢,而且會占用比較大的IO,應該選擇在應用不是很繁忙的時候做這項工作,否則會對應用的正常運行產生較大的影響。對于這種方法還存在有一個比較大的弊端,就是在EXP表的時候要保證該表是沒有數據的更新或者是只讀狀態的,不能對表有插入或者更新操作,否則會導致數據的丟失。


SQL> select count(*) from test;

  COUNT(*)

----------

169344

SQL> truncate table chained_rows;

Table truncated.

SQL> analyze table test LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL> select count(*) from chained_rows;

  COUNT(*)

----------

      3294

$ exp allan/allan file=test.dmp tables=test


Export: Release 9.2.0.3.0 - ProdUCtion on Sun Jun 6 13:50:08 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

Export done in ZHS16GBK character set and AL16UTF16 NCHAR character set

About to export specified tables via Conventional Path ...

. . exporting table                           TEST     169344 rows exported

Export terminated successfully without warnings.

$ sqlplus allan/allan

SQL*Plus: Release 9.2.0.3.0 - Production on Sun Jun 6 13:50:43 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved

Connected to:

Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

SQL> truncate table test;

Table truncated.

SQL> exit

Disconnected from Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

$ imp allan/allan file=test.dmp full=y ignore=y buffer=5000000

Import: Release 9.2.0.3.0 - Production on Sun Jun 6 13:51:24 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path

import done in ZHS16GBK character set and AL16UTF16 NCHAR character set

. importing ALLAN's objects into ALLAN

. . importing table                         "TEST"     169344 rows imported

Import terminated successfully without warnings.

$ sqlplus allan/allan

SQL*Plus: Release 9.2.0.3.0 - Production on Sun Jun 6 13:52:53 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:

Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

SQL> select count(*) from test;

  COUNT(*)

----------

    169344

SQL> select index_name from user_indexes where table_name='TEST';       


INDEX_NAME

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

OBJ_INDEX

SQL> alter index OBJ_INDEX rebuild online;

Index altered.

SQL> truncate table chained_rows;

Table truncated.

SQL> analyze table test LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL> select count(*) from chained_rows;

  COUNT(*)

----------

         0


 

  方法五:使用MOVE命令來清除行遷移的方法

1.    查看要清除行遷移的表所在的表空間。 


Select table_name,tablespace_name from user_tables where table_name='table_name’;


2.    查看要清除行遷移的表上的具體索引。


select index_name,table_name from user_indexes where table_name=‘table_name’;


3.    Move要清除RM的表到指定的表空間中去。


alter table table_name move tablespace tablespace_name;


4.    重建表上的所有索引。


alter index index_name rebuild;


這種方法適用于8i及其以上的數據庫版本,主要是利用數據庫的一個MOVE命令來實現行遷移的清除的,MOVE命令的實質其實就是INSERT … SELECT的一個過程,在MOVE表的過程中是需要兩倍的原來的表大小的,因為中間過程是要保留原來的舊表的,新表創建完成后舊表就被刪除并釋放空間了。MOVE的時候要注重后面一定要加上表空間參數,所以必須要先知道表所在的表空間;因為MOVE表之后需要重建索引,所以之前要確定表上的所有的索引。

這種方法對于表記錄數很大或者表上索引太多的情況不太適用,因為本身的MOVE就會很慢, 而且MOVE表的時候會要鎖定表,時間長了會導致對表的其他操作出現問題,導致數據插入不了丟失數據;MOVE表后還要重建索引,索引太多了的話重建的時間也會太長;再者,這個方法也比較消耗資源,因此強烈建議在業務不繁忙的時候再執行。

以下是一個具體在生產數據庫上清除行遷移的例子,在這之前已經調整過表的pctfree參數至一個合適的值了:


SQL>ANALYZE TABLE SERVICE LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL>SELECT count(*) from chained_rows;

COUNT(*)

----------

    9145

SQL>select table_name,tablespace_name from user_tables where table_name='SERVICE';

TABLE_NAME                     TABLESPACE_NAME

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

SERVICE                        DATA

SQL>select index_name,table_name from user_indexes where table_name='SERVICE';

INDEX_NAME                     TABLE_NAME

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

I_SERVICE_ACCOUNTNUM           SERVICE

I_SERVICE_DATEACTIVATED        SERVICE

I_SERVICE_SC_S                 SERVICE

I_SERVICE_SERVICECODE          SERVICE

PK_SERVICE_SID                 SERVICE

SQL>select count(*) from SERVICE;


  COUNT(*)

----------

    518718

SQL>alter table SERVICE move tablespace DATA;

Table altered.

SQL>alter index I_SERVICE_ACCOUNTNUM rebuild;

Index altered.

SQL>alter index I_SERVICE_DATEACTIVATED rebuild;

Index altered.

SQL>alter index I_SERVICE_SC_S rebuild;

Index altered.

SQL>alter index I_SERVICE_SERVICECODE rebuild;

Index altered.

SQL>alter index PK_SERVICE_SID rebuild;

Index altered.

SQL>truncate table chained_rows;

Table truncated.

SQL>ANALYZE TABLE SERVICE LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL>SELECT count(*) from chained_rows;

COUNT(*)

----------

         0


利用MOVE命令來清除行遷移,執行的命令都相對比較的簡單,上面的例子中清除表SERVCIE中的行遷移的時間大概在五分鐘左右,其中move命令執行的時間為不到兩分鐘,也就是鎖表的時間大概是不到兩分鐘,對于大多數的應用來說一般問題都是不大的,放在系統閑的時候執行基本上不會對應用產生什么太多的影響。

 

  方法六:對于一些行遷移數量巨大而且表記錄數巨大的表的行遷移的清除方法

1.  使用TOAD工具或者別的方法獲取存在有大量行遷移并且表記錄很大的表的重建表的SQL,然后保存為腳本。

2.  使用RENAME命令將原始表重命名為一個備份表,然后刪除別的表對原始表上的限制、以及原始表上的外鍵和索引。

3.  利用1中生成的腳本重建原始表,以及表上的限制,外鍵,索引等對象。

4.  然后按表模式導出2中備份的表,然后導入到另外的一個臨時中轉的數據庫庫中,因為表的名字已經改變,所以導入后需要RENAME表為原來的名字,然后重新導出,最后再導入到原來的數據庫中。

這種方法主要是用來針對一些數據量比較大,并且表上的行遷移也比較多的表的行遷移清除。對于這些大表的行遷移的清除,正常來說都需要停應用一段較長時間才能夠清除掉,讓人感覺比較的頭疼,對于7*24小時的應用來說,down機的時間越長損失則越大,當然是要盡量的減短down機的時間。但是因為表本身比較大,不管怎樣做什么操作都是會比較耗費時間和資源的,但是假如應用在某段時間內主要是以插入數據為主,更新數據和刪除數據都很少的,因此可以考慮可以采用這么一種方法:先重命名表,然后重新建立一個和原來一樣的表,用來保證之后的應用的數據是可以正常插入的,從而使應用不用停很久,因為重建一個沒有任何數據的表結構的過程是很短暫的,大概需要幾秒鐘的時間,而重建好表了后就能保證應用能夠正常的寫入數據,從而使應用幾乎不用停頓,然后把開始重命名的原始表按表模式導出,因為表的名字已經被改變,因此需要一個臨時庫來導入這些數據,然后重命名回原來的名字,然后按原來的表名導出后再重新導入原始數據庫,這樣操作起來雖然會比較麻煩,但是卻是一種很有效很實際的方法,速度也很快,導出后導入,因為本身表結構已經建立好了,不需要其他任何的多的操作,而且最要害的是這種方法所需要的down機時間是最短的。


SQL>ALTER TABLE USER.PAY RENAME TO PAY_X ;

然后導出PAY_X表;

$ exp USER/USER file=PAY_X.dmp tables=PAY_X

SQL>ALTER TABLE USER.BATCHPAYMENTDETAIL DROP CONSTRAINT FK_BATCHPAYMENTDETAIL_OPAYID ;

SQL>ALTER TABLE USER.DEPOSITCLASSIFY DROP CONSTRAINT FK_DEPOSITCLASSIFY2 ;  

SQL>ALTER TABLE USER.DEPOSITCREDITLOG DROP CONSTRAINT FK_DEPOSITCREDITLOG2 ;     

SQL>ALTER TABLE USER.DEPOSIT DROP CONSTRAINT SYS_C003423 ;

SQL>ALTER TABLE USER.PAY_X DROP CONSTRAINT SYS_C003549 ;

SQL>DROP INDEX USER.I_PAY_STAFFID ;

SQL>CREATE TABLE USER.PAY

(

  PAYID           NUMBER(9),

  ACCOUNTNUM          NUMBER(9),

  TOTAL               NUMBER(12,2),

  PREVPAY         NUMBER(12,2),

  PAY             NUMBER(12,2),


  STAFFID             NUMBER(9),

  PROCESSDATE         DATE,

  PAYNO           CHAR(12),

  TYPE                CHAR(2)                   DEFAULT '0',

  PAYMENTMETHOD       CHAR(1)                   DEFAULT '0',

  PAYMENTMETHODID     VARCHAR2(20),

  BANKACCOUNT         VARCHAR2(32),

  PAYMENTID           NUMBER(9),

  STATUS              CHAR(1)                   DEFAULT '0',

  MEMO                VARCHAR2(255),

  SERVICEID           NUMBER(9),

  CURRENTDEPOSITID    NUMBER(9),

  SHOULDPROCESSDATE   DATE                      DEFAULT sysdate,

  ORIGINALEXPIREDATE  DATE,

  ORIGINALCANCELDATE  DATE,

  EXPIREDATE          DATE,

  CANCELDATE          DATE,

  DEPOSITTYPE         CHAR(1)

)

TABLESPACE USER

PCTUSED    95

PCTFREE    5

INITRANS   1

MAXTRANS   255

STORAGE    (

            INITIAL          7312K

            NEXT             80K

            MINEXTENTS       1

            MAXEXTENTS       2147483645

            PCTINCREASE      0

            FREELISTS        1

            FREELIST GROUPS  1


            BUFFER_POOL      DEFAULT

           )

NOLOGGING

NOCACHE

NOPARALLEL;

SQL>CREATE INDEX USER.I_PAY_STAFFID ON USER.PAY

(STAFFID)

NOLOGGING

TABLESPACE USER

PCTFREE    5

INITRANS   2

MAXTRANS   255

STORAGE    (

            INITIAL          1936K

            NEXT             80K

            MINEXTENTS       1

            MAXEXTENTS       2147483645

            PCTINCREASE      0

            FREELISTS        1

            FREELIST GROUPS  1

            BUFFER_POOL      DEFAULT

           )

NOPARALLEL;

SQL>CREATE UNIQUE INDEX USER.PK_PAY_ID ON USER.PAY

(PAYID)

NOLOGGING

TABLESPACE USER

PCTFREE    5

INITRANS   2

MAXTRANS   255

STORAGE    (

            INITIAL          1120K

            NEXT             80K

            MINEXTENTS       1

            MAXEXTENTS       2147483645

            PCTINCREASE      0

            FREELISTS        1

            FREELIST GROUPS  1

            BUFFER_POOL      DEFAULT

           )

NOPARALLEL;

SQL>ALTER TABLE USER.PAY ADD (


  FOREIGN KEY (STAFFID)

REFERENCES USER.STAFF (STAFFID));

SQL>ALTER TABLE USER.DEPOSITCLASSIFY ADD

 CONSTRAINT FK_DEPOSITCLASSIFY2

 FOREIGN KEY (PAYID)

  REFERENCES USER.PAY (PAYID)  ;  

SQL>ALTER TABLE USER.DEPOSITCREDITLOG ADD

 CONSTRAINT FK_DEPOSITCREDITLOG2

 FOREIGN KEY (PAYID)

  REFERENCES USER.PAY (PAYID)  ;  

SQL>ALTER FUNCTION "USER"."GENERATEPAYNO" COMPILE ; 

SQL>ALTER PROCEDURE "USER"."ENGENDERPRVPAY" COMPILE ;  

SQL>ALTER PROCEDURE "USER"."ISAP_ENGENDERPRVPAY" COMPILE ;  

SQL>ALTER PROCEDURE "USER"."SPADDCREDITDEPOSIT" COMPILE ;  

SQL>ALTER PROCEDURE "USER"."SPADDDEPOSITWITHOUTCARD" COMPILE ;  

SQL>ALTER PROCEDURE "USER"."SPADJUSTLWDEPOSIT" COMPILE ;  

……

然后將導出的表PAY_X的dmp文件導入一個臨時的數據庫中,然后在臨時數據庫中將其表名重新命名為PAY,再按表模式將其導出。

imp USER/USER file= PAY_x.dmp tables=PAY ignore=y

SQL>rename PAY_X to PAY;

$ exp USER/USER file=PAY.dmp tables=PAY

最后將這個dmp文件導入正式的生產數據庫中即可。


以上的過程在重建好PAY表后整個應用就恢復正常了,而重命名表后重建表的時間是非常之短的,我測試的時間大概是在幾分鐘之內就可以做完了,新數據就可以插入表了,剩下的工作就是將舊的數據導入數據庫,這個工作的時間要求上就沒有那么高了,因為應用已經正常了,一般來說利用晚上業務不忙的時候都可以把一張表的數據導入完成的。

  以上的六種清除行遷移的方法各有各自的優缺點,分別適用于不同的情況下使用,利用以上的幾種清除行遷移的方法基本上就能完全清除掉系統中的存在的行遷移了,當然,具體的生產環境中還需要具體問題具體分析的,針對不同類型的系統,系統中不同特點的表采用不同的清除方法,盡量的減少停數據庫的時間,以保證應用的不間斷穩定運行。 

 



                               圖一:Oracle Block結構圖

 

 

由上圖我們可以看出,一個Oracle block由三個部分組成,分別是數據塊頭、自由空間、實際數據三部份組成。

數據塊頭:主要包含有數據塊地址的一些基本信息和段的類型,以及表和包含有數據的實際行的地址。

自由空間:是指可以為以后的更新和插入操作分配的空間,大小由PCTFREE和PCTUSED兩個參數影響。

實際數據:是指在行內存儲的實際數據。

  當創建或者更改任何表和索引的時候,Oracle在空間控制方面使用兩個存儲參數:

  PCTFREE:為將來更新已經存在的數據預留空間的百分比。

  PCTUSED:用于為插入一新行數據的最小空間的百分比。這個值決定了塊的可用狀態。可用的塊時可以執行插入的塊,不可用狀態的塊只能執行刪除和修改,可用狀態的塊被放在freelist中。

  當表中一行的數據不能在一個數據block中放入的時候,這個時候就會發生兩種情況,一種是行鏈接,另外一種就是行遷移了。

  行鏈接產生在第一次插入數據的時候假如一個block不能存放一行記錄的情況下。這種情況下,Oracle將使用鏈接一個或者多個在這個段中保留的block存儲這一行記錄,行鏈接比較輕易發生在比較大的行上,例如行上有LONG、LONG RAW、LOB等數據類型的字段,這種時候行鏈接是不可避免的會產生的。

  當一行記錄初始插入的時候事可以存儲在一個block中的,由于更新操作導致行長增加了,而block的自由空間已經完全滿了,這個時候就產生了行遷移。在這種情況下,Oracle將會遷移整行數據到一個新的block中(假設一個block中可以存儲下整行數據),Oracle會保留被遷移行的原始指針指向新的存放行數據的block,這就意味著被遷移行的ROW ID是不會改變的。

  當發生了行遷移或者行鏈接,對這行數據操作的性能就會降低,因為Oracle必須要掃描更多的block來獲得這行的信息。

  下面舉例來具體說明行遷移/行鏈接的產生過程。

  先創建一個pctfree為20和pctused為50的測試表:

  create table test(

  col1 char(20),

  col2 number)

  storage (

  pctfree 20

  pctused 50);

  當插入一條記錄的時候,Oracle會在free list中先去尋找一個自由的塊,并且將數據插入到這個自由塊中。而在free list中存在的自由的塊是由pctfree值決定的。初始的空塊都是在free list中的,直到塊中的自由空間達到pctfree的值,此塊就會從free list中移走,而當此塊中的使用空間低于pctused的時候,此塊又被重新放到free list中。


  Oracle使用free list機制可以大大的提高性能,對于每次的插入操作,Oracle只需要查找free list就可以了,而不是去查找所有的block來尋找自由空間。

  假設第一次插入數據使用的一個空的block,如下圖所示:



圖二:Oracle空的block結構圖

假設插入第一條記錄的時候占用一個block的10%的空間(除去block頭占去的大小),剩余的自由空間90%大于pctfree20%,因此這個block還將繼續為下次的插入操作提供空間。



圖三:插入10%后的Oracle block結構圖

再連續插入七條記錄,使block的剩余自由空間剩下20%,此時,這個block將要從free list中移走,假如再插入記錄,Oracle將再free list中尋找下一個空余的block去存放后來插入的數據。



圖四:插入80%后的Oracle block結構圖

  此時假如去更新第一條插入的記錄,使其行長增加15%,Oracle將會使用這個block中剩余的20%的自由空間來存放此行數據,假如再更新第二條記錄,同樣的使其行長增加15%,而此block中只剩下5%的自由空間,不夠存放更新的第二條記錄,于是Oracle會在free list中尋找一個有自由空間(10%+15%)的block來存放這行記錄的block去存儲,在原來的block中保存了指向新的block的指針,原來這行記錄的ROW ID保持不變,這個時候就產生了行遷移。

  而當我們插入一條新紀錄的時候,假如一個blcok不足以存放下這條記錄,Oracle就會尋找一定數量的block一起來容納這條新的記錄,這個時候就產生了行鏈接,行鏈接主要產生在LOB、CLOB、BLOB和大的VA行鏈接HAR2數據類型上。

  具體我們通過下面的一個試驗來查看行鏈接和行遷移是如何產生并在數據文件中體現出來的。

 

 


先查看ALLAN這個表空間的數據文件號,為了便于測試,我只建立了一個數據文件。

SQL> select file_id from dba_data_files where tablespace_name='ALLAN';

   FILE_ID

----------

        23

創建一個測試表test:

SQL> create table test ( x int primary key, a char(2000), b char(2000), c char(2000), d char(2000), e char(2000) ) tablespace allan;

Table created.

因為我的數據庫的db_block_size是8K,所以我創建的表有五個字段,每個占2000個字節,這樣一行記錄大約10K,就能超過一個block的大小了。

然后插入一行記錄,只有一個字段的:

SQL> insert into test(x) values (1);

1 row created.

SQL> commit;

Commit complete.

查找這行記錄所在的block,并dump出來:

SQL> select dbms_rowid.rowid_block_number(rowid) from test;

DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID)

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

                                  34

SQL> alter system dump datafile 23 block 34;

System altered.

在udump目錄下查看trace文件的內容如下:

Start dump data blocks tsn: 34 file#: 23 minblk 34 maxblk 34

buffer tsn: 34 rdba: 0x05c00022 (23/34)

scn: 0x0000.013943f3 seq: 0x01 flg: 0x02 tail: 0x43f30601

frmt: 0x02 chkval: 0x0000 type: 0x06=trans data

Block header dump:  0x05c00022

 Object id on Block? Y

 seg/obj: 0x3ccd  csc: 0x00.13943ef  itc: 2  flg: O  typ: 1 - DATA

     fsl: 0  fnx: 0x0 ver: 0x01

Itl           Xid                  Uba         Flag  Lck        Scn/Fsc

0x01   0x000a.02e.00000ad7  0x00800036.03de.18  --U-    1  fsc 0x0000.013943f3

0x02   0x0000.000.00000000  0x00000000.0000.00  ----    0  fsc 0x0000.00000000


data_block_dump,data header at 0xadb505c

===============

tsiz: 0x1fa0

hsiz: 0x14

pbl: 0x0adb505c

bdba: 0x05c00022

     76543210

flag=--------

ntab=1

nrow=1

frre=-1

fsbo=0x14

fseo=0x1f9a

avsp=0x1f83

tosp=0x1f83

0xe:pti[0]      nrow=1  offs=0

0x12:pri[0]     offs=0x1f9a

block_row_dump:

tab 0, row 0, @0x1f9a

tl: 6 fb: --H-FL-- lb: 0x1  cc: 1

col  0: [ 2]  c1 02

end_of_block_dump

End dump data blocks tsn: 34 file#: 23 minblk 34 maxblk 34

對其中的一些信息做一些解釋:

Fb:H是指行記錄的頭,L是指行記錄的最后一列,F是指行記錄的第一列。

Cc:列的數量

Nrid:對于行鏈接或者行遷移來說的下一個row id的值

由上面的dump信息我們可以看出來當前表test是沒有行鏈接或者行遷移的。

然后更新test表,并重新dump出來:

SQL> update test set a='test',b='test',c='test',d='test',e='test' where x=1;

1 row updated.

SQL> commit;

Commit complete.

此時應該有行遷移/行鏈接產生了。

SQL> alter system dump datafile 23 block 34;

System altered.

在udump目錄下查看trace文件的內容如下:

Start dump data blocks tsn: 34 file#: 23 minblk 34 maxblk 34

buffer tsn: 34 rdba: 0x05c00022 (23/34)

scn: 0x0000.0139442b seq: 0x01 flg: 0x02 tail: 0x442b0601

frmt: 0x02 chkval: 0x0000 type: 0x06=trans data

Block header dump:  0x05c00022

 Object id on Block? Y

 seg/obj: 0x3ccd  csc: 0x00.1394429  itc: 2  flg: -  typ: 1 - DATA

     fsl: 0  fnx: 0x0 ver: 0x01

 Itl           Xid                  Uba         Flag  Lck        Scn/Fsc

0x01   0x000a.02e.00000ad7  0x00800036.03de.18  C---    0  scn 0x0000.013943f3

0x02   0x0004.002.00000ae0  0x0080003b.0441.11  --U-    1  fsc 0x0000.0139442b

data_block_dump,data header at 0xadb505c

===============

tsiz: 0x1fa0

hsiz: 0x14

pbl: 0x0adb505c

bdba: 0x05c00022

     76543210

flag=--------

ntab=1

nrow=1

frre=-1

fsbo=0x14

fseo=0x178a

avsp=0x177c

tosp=0x177c

0xe:pti[0]      nrow=1  offs=0

0x12:pri[0]     offs=0x178a

block_row_dump:

tab 0, row 0, @0x178a

tl: 2064 fb: --H-F--N lb: 0x2  cc: 3

nrid:  0x05c00023.0

col  0: [ 2]  c1 02

col  1: [2000]

 74 65 73 74 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20

 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20


 …………

col  2: [48]

 74 65 73 74 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20

 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20

end_of_block_dump

End dump data blocks tsn: 34 file#: 23 minblk 34 maxblk 34

我們不難看出,nrid出現了值,指向了下一個row id,證實剛剛的update操作使這行記錄產生了行鏈接或者行遷移了。


 

二、行遷移/行鏈接的檢測

  通過前面的介紹我們知道,行鏈接主要是由于數據庫的db_block_size不夠大,對于一些大的字段沒法在一個block中存儲下,從而產生了行鏈接。對于行鏈接我們除了增大db_block_size之外沒有別的任何辦法去避免,但是因為數據庫建立后db_block_size是不可改變的(在9i之前),對于Oracle9i的數據庫我們可以對不同的表空間指定不同的db_block_size,因此行鏈接的產生幾乎是不可避免的,也沒有太多可以調整的地方。行遷移則主要是由于更新表的時候,由于表的pctfree參數設置太小,導致block中沒有足夠的空間去容納更新后的記錄,從而產生了行遷移。對于行遷移來說就非常有調整的必要了,因為這個是可以調整和控制清除的。

  如何檢測數據庫中存在有了行遷移和行鏈接呢?我們可以利用Oracle數據庫自身提供的腳本utlchain.sql(在$ORACLE_HOME/rdbms/admin目錄下)生成chained_rows表,然后利用ANALYZE TABLE table_name LIST CHAINED ROWS INTO chained_rows命令逐個分析表,將分析的結果存入chained_rows表中。從utlchain.sql腳本中我們看到chained_rows的建表腳本,對于分區表,cluster表都是適用的。然后可以使用拼湊語句的辦法生成分析所需要的表的腳本,并執行腳本將具體的分析數據放入Chained_rows表中,例如下面是分析一個用戶下所有表的腳本:


SPOOL list_migation_rows.sql

SET ECHO OFF

SET HEADING OFF

SELECT 'ANALYZE TABLE ' table_name ' LIST CHAINED ROWS INTO chained_rows;' FROM user_tables;

SPOOL OFF


然后查詢chained_rows表,可以具體查看某張表上有多少的行鏈接和行遷移。


SELECT table_name, count(*) from chained_rows GROUP BY table_name;


當然,也可以查詢v$sysstat視圖中的’table fetch continued row’列得到當前的行鏈接和行遷移數量。


SELECT name, value FROM v$sysstat WHERE name = 'table fetch continued row';


可以使用如下的腳本來直接查找存在有行鏈接和行遷移的表,自動完成所有的分析和統計。


accept owner prompt " Enter the schema name to check for Row Chaining (RETURN for All): "
prompt
prompt
accept table prompt " Enter the table name to check (RETURN for All tables owned by &owner): "
prompt
prompt
set head off serverout on term on feed off veri off echo off
!clear
prompt
declare
v_owner varchar2(30);
v_table varchar2(30);
v_chains number;
v_rows number;
v_count number := 0;
sql_stmt varchar2(100);
dynamicCursor INTEGER;
dummy INTEGER;
cursor chains is
select count(*) from chained_rows;
cursor analyze is
select owner, table_name
from sys.dba_tables
where owner like upper('%&owner%')
and table_name like upper('%&table%')
order by table_name;
begin
dbms_output.enable(64000);
open analyze;
fetch analyze into v_owner, v_table;
while analyze%FOUND loop
dynamicCursor := dbms_sql.open_cursor;
sql_stmt := 'analyze table 'v_owner'.'v_table' list chained rows into chained_rows';
dbms_sql.parse(dynamicCursor, sql_stmt, dbms_sql.native);
dummy := dbms_sql.execute(dynamicCursor);
dbms_sql.close_cursor(dynamicCursor);
open chains;
fetch chains into v_chains;
if (v_chains != 0) then
if (v_count = 0) then
dbms_output.put_line(CHR(9)CHR(9)CHR(9)'<<<<< Chained Rows Found >>>>>');

v_count := 1;
end if;
dynamicCursor := dbms_sql.open_cursor;
sql_stmt := 'Select count(*) v_rows'' From 'v_owner'.'v_table;
dbms_sql.parse(dynamicCursor, sql_stmt, dbms_sql.native);
dbms_sql.DEFINE_COLUMN(dynamicCursor, 1, v_rows);
dummy := dbms_sql.execute(dynamicCursor);
dummy := dbms_sql.fetch_rows(dynamicCursor);
dbms_sql.COLUMN_VALUE(dynamicCursor, 1, v_rows);
dbms_sql.close_cursor(dynamicCursor);
dbms_output.put_line(v_owner'.'v_table);
dbms_output.put_line(CHR(9)'---> Has 'v_chains' Chained Rows and 'v_rows' Num_Rows in it!');
dynamicCursor := dbms_sql.open_cursor;
sql_stmt := 'truncate table chained_rows';
dbms_sql.parse(dynamicCursor, sql_stmt, dbms_sql.native);
dummy := dbms_sql.execute(dynamicCursor);
dbms_sql.close_cursor(dynamicCursor);
v_chains := 0;
end if;
close chains;
fetch analyze into v_owner, v_table;
end loop;
if (v_count = 0) then
dbms_output.put_line('No Chained Rows found in the 'v_owner' owned Tables!');
end if;
close analyze;
end;
/
set feed on head on
prompt


 

三、行遷移和行鏈接的清除

由于對于行鏈接來說只能增大db_block_size來清除,而db_block_size在創建了數據庫后又是不能改變了的,所以這里對行鏈接的清除不做過多的敘述了,主要是針對行遷移來談談在實際的生產系統中如何去清除。

對于行遷移的清除,一般來說分為兩個步驟:第一步,控制住行遷移的增長,使其不在增多;第二步,清除掉以前存在的行遷移。

眾所周知,行遷移產生的主要原因是因為表上的pctfree參數設置過小導致的,而要實現第一步控制住行遷移的增長,就必須設置好一個正確合適的pctfree參數,否則即使清除了當前的行遷移后馬上又會產生很多新的行遷移。當然,這個參數也不是越大越好的,假如pctfree設置的過大,會導致數據塊的利用率低,造成空間的大量浪費,因此必須設置一個合理的pctfree參數。如何去確定一個表上合理的pctfree參數呢,一般來說有兩種方法。

第一種是定量的的設定方法,就是利用公式來設定pctfree的大小。先使用ANALYZE TABLE table_name ESTIMATE  STATISTICS命令來分析要修改pctfree的表,然后查看user_tables中的AVG_ROW_LEN列值,得到一個平均行長AVG_ROW_LEN1,然后大量的對表操作之后,再次使用上述命令分析表,得到第二個平均行長AVG_ROW_LEN2,然后運用公式100 * (AVG_ROW_LEN2-AVG_ROW_LEN1)/(AVG_ROW_LEN2-AVG_ROW_LEN1 + 原始的AVG_ROW_LEN)得出的結果就是定量計算出來的一個合適的pctfree的值。這種方法因為是定量計算出來的,可能不一定會很準確,而且因為要分析表,所以對于使用RBO執行計劃的系統不是很適用。例如:avg_row_len_1 = 60,avg_row_len_2 = 70,則平均修改量為 10,PCTFREE 應調整為 100 * 10 /(10 + 60)= 16.7% 。

第二種是差分微調的方法,先查詢到當前表的pctfree的值,然后監控和調整pctfree參數,每次增加一點pctfree的大小,每次增加的比例不要超過5個百分點,然后使用ANALYZE TABLE TABLE_NAME LIST CHAINED ROWS INTO chained_rows命令分析每次所有的行遷移和行鏈接的增長情況,對于不同的表采取不同的增長比例,對于行遷移增長的比較快的表pctfree值就增加的多點,對于增長慢的表就增加的少點,直到表的行遷移基本保持不增長了為止。但是注重不要把pctfree調的過大,一般在40%以下就可以了,否則會造成空間的很大浪費和增加數據庫訪問的IO。

使用上述的方法控制住了當前表的行遷移的增長之后,就可以開始清除之前表上存在的行遷移了。是否清除掉行遷移,關系到系統的性能是否能夠有很大的提高。因此,對于以前存在的行遷移是一定而且必須要清除掉的。清除掉已經存在的行遷移有很多方法,但是并不是所有的方法都能適用所有的情況,例如表中的記錄數多少,表上的關聯多少、表上行遷移的數量多少等等這些因素都會是成為制約你使用什么方法清除的條件,因此,根據表的特點和具體情況的不同我們應該采用不同的方法去清除行遷移。下面我將逐一介紹各種清除行遷移的方法以及它們各自適用的不同情況。

方法一:傳統的清除行遷移的方法

具體步驟如下:

1.  執行$ORACLE_HOME/rdbms/admin目錄下的utlchain.sql腳本創建chained_rows表。


@$ORACLE_HOME/rdbms/admin/utlchain.sql


2.  將存在有行遷移的表(用table_name代替)中的產生行遷移的行的rowid放入到chained_rows表中。  


ANALYZE TABLE table_name LIST CHAINED ROWS INTO chained_rows;


3.  將表中的行遷移的row id放入臨時表中保存。


CREATE  TABLE  table_name_temp  AS


SELECT  *  FROM  table_name

WHERE  rowid  IN

(SELECT  head_rowid  FROM  chained_rows

WHERE  table_name  =  'table_name');


4.  刪除原來表中存在的行遷移的記錄行。


DELETE  table_name

WHERE  rowid  IN

(SELECT  head_rowid

FROM  chained_rows

WHERE  table_name  =  'table_name');


5.  從臨時表中取出并重新插入那些被刪除了的數據到原來的表中,并刪除臨時表。


INSERT  INTO  table_name  SELECT  *  FROM  table_name_temp;

DROP  TABLE  table_name_temp;


對于這種傳統的清除RM的方法,優點是執行起來過程比較簡單,輕易實現。但是這種算法的缺陷是沒有考慮到表關聯的情況,在大多數數據庫中很多表都是和別的表之間有表關聯的,有外鍵的限制,這樣就造成在步驟3中根本無法delete掉存在有行遷移的記錄行,所以這種方法能夠適用的表的范圍是有限的,只能適用于表上無任何外鍵關聯的表。由于這種方法在插入和刪除數據的時候都沒有disable掉索引,這樣導致主要消耗時間是在刪除和插入時維持索引樹的均衡上了,這個對于假如記錄數不多的情況時間上還比較短,但是假如對于記錄數很多的表這個所消耗的時間就不是能夠接受的了。顯然,這種方法在處理大數據量的表的時候顯然是不可取的。

以下是一個具體在生產數據庫上清除行遷移的例子,在這之前已經調整過表的pctfree參數至一個合適的值了:


SQL>@$ORACLE_HOME/rdbms/admin/utlchain.sql

Table created.

SQL> ANALYZE TABLE CUSTOMER LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL>SELECT count(*) from chained_rows;

TABLE_NAME                       COUNT(*)

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

CUSTOMER                            21306

1 rows selected.

查看在CUSTOMER表上存在的限制:

SQL>select CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where TABLE_NAME='CUSTOMER';

CONSTRAINT_NAME                C TABLE_NAME

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

PK_CUSTOMER1                   P CUSTOMER

SQL>select CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where R_CONSTRAINT_NAME='PK_CUSTOMER1';

no rows selected

SQL> CREATE  TABLE  CUSTOMER_temp  AS

SELECT  *  FROM  CUSTOMER   WHERE  rowid  IN

(SELECT  head_rowid  FROM  chained_rows

WHERE  table_name  =  'CUSTOMER');

Table created.

SQL>select count(*) from CUSTOMER;

  COUNT(*)

----------

    338299

SQL> DELETE  CUSTOMER WHERE rowid  IN

(SELECT  head_rowid

FROM  chained_rows

WHERE  table_name  =  'CUSTOMER');

21306 rows deleted.

SQL> INSERT  INTO  CUSTOMER SELECT  *  FROM  CUSTOMER_temp;

21306 rows created.


SQL> DROP  TABLE  CUSTOMER_temp;

Table dropped.

SQL> commit;

Commit complete.

SQL> select count(*) from CUSTOMER;

  COUNT(*)

----------

338299

SQL> truncate table chained_rows;

Table truncated.

SQL> ANALYZE TABLE CUSTOMER LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL> select count(*) from chained_rows;

  COUNT(*)

----------

         0


以上整個清除兩萬多行的行遷移過程在三分鐘左右,而且全部都在聯機的狀態下完成,基本上不會對業務有什么影響,唯一就是在要清除行遷移的表上不能有對外鍵的限制,否則就不能采用這個方法去清除了。

 

  方法二:改進了的傳統清除行遷移的方法

1.  執行$ORACLE_HOME/rdbms/admin目錄下的utlchain.sql腳本創建chained_rows表。

2.  禁用所有其它表上關聯到此表上的所有限制。

3.  將表中的行遷移的row id放入臨時表中保存。

4.  刪除原來表中存在的行遷移的記錄行。

5.  從臨時表中取出并重新插入那些被刪除了的數據到原來的表中,并刪除臨時表。

6.  啟用所有其它表上關聯到此表上的所有限制。

這種算法是對傳統算法的一種改進,對于使用這種算法來清除行遷移,考慮到了表之間的關聯,還可以靈活的利用的TOAD工具生成的表關聯信息,是一種比較適合于清除行遷移的一種方法。但是因為使用這種方法后來需要重建索引,假如記錄數很大,比如說上千萬條以上的記錄的表,就不是很合適了,因為這個重建索引的時間會很長,是線性時間復雜度的,而重建索引是會導致索引所在的表被鎖定的,從而導致插入不了新的記錄,重建索引的時間太長導致記錄長時間插入不了是會嚴重影響應用的,甚至導致數據的丟失,因此這個是使用這個方法前必須要考慮到的一個重要因素;對于8i以上的版本可以使用online的方法來重建索引,這樣不會導致鎖表,但是會有額外更多的開銷,時間會很長。再者,因為這種方法在插入記錄和刪除記錄都是帶著索引的,假如表上的行遷移比較多,這樣耗時間會比較長,而且占用資源也會比較大,因此只適用于表上行遷移存在的比較少的表。總的來說,這種方法對于表記錄太多或者是表上的行遷移太多的情況都不是很適用,比較適合表記錄少和表上行遷移都不太多的情況。

以下是一個具體在生產數據庫上清除行遷移的例子,在這之前已經調整過表的pctfree參數至一個合適的值了:


SQL>select index_name,index_type,table_name from user_indexes where table_name='TERMINAL';

INDEX_NAME                     INDEX_TYPE         TABLE_NAME

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

INDEX_TERMINAL_TERMINALCODE    NORMAL              TERMINAL

I_TERMINAL_ID_TYPE             NORMAL              TERMINAL

I_TERMINAL_OT_OID              NORMAL              TERMINAL

PK_TERMINAL_ID                 NORMAL              TERMINAL

UI_TERMINAL_GOODIS_SSN         NORMAL              TERMINAL

SQL>select  CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where R_CONSTRAINT_NAME='PK_TERMINAL_ID';

CONSTRAINT_NAME                C TABLE_NAME


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

SYS_C003200                    R CONN

SQL>alter table CONN disable constraint SYS_C003200;

Table altered.

SQL>CREATE  TABLE  TERMINAL_temp  AS

SELECT  *  FROM  TERMINAL

WHERE  rowid  IN

(SELECT  head_rowid  FROM  chained_rows

WHERE  table_name  =  'TERMINAL'); 

Table created.

SQL>select count(*) from TERMINAL_temp;

  COUNT(*)

----------

      8302

SQL>DELETE  TERMINAL

WHERE  rowid  IN

(SELECT  head_rowid

FROM  chained_rows

WHERE  table_name  =  'TERMINAL');

 8302 rows deleted.

SQL>INSERT  INTO  TERMINAL SELECT  *  FROM  TERMINAL_temp;

8302 rows created.

SQL>alter table CONN disable constraint SYS_C003200;

Table altered.

SQL>select count(*) from terminal;

  COUNT(*)

----------

647799

SQL>truncate table chained_rows;

Table truncated.

SQL>ANALYZE TABLE TERMINAL LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL>select count(*) from chained_rows;

  COUNT(*)

----------

         0


從上面過程中可以看出,對TERMINAL這張表的行遷移清除耗時總共不到五分鐘的時間,總體來說還是比較快的。從我在生產數據庫中清除行遷移的經驗來說,這種方法基本適用于大部分存在有行遷移的表。

 

  方法三:使用TOAD工具清除行遷移的方法

1.  備份要清除RM的表。


RENAME table_name TO table_name_temp;


2.  Drop 所有其它表上關聯到table_name的外鍵限制。


SELECT CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where R_CONSTRAINT_NAME in (SELECT CONSTRAINT_NAME from USER_CONSTRAINTS where TABLE_NAME='table_name' AND CONSTRAINT_TYPE=’P’);

ALTER TABLE table_name DROP CONSTRAINT XXXX;(XXXX為上述的查詢結果)


3.  重建1中被rename的表。


CREATE TABLE table_name AS SELECT * FROM table_name_temp WHERE 0 = 1;


4.  重建表中原來的數據。


INSERT /*+ APPEND */ INTO table_name SELECT * FROM table_name_temp;


5.  刪除在table_name_temp上的索引和關聯其他表的外鍵。

6.  在table_name上建立和原來一樣的索引、主鍵和所有的外鍵限制。

7.  重新編譯相關的存儲過程、函數和包。

8.  刪除表table_name_temp。

對于使用這種方法來清除行遷移,全部的代碼都是可以由TOAD工具來生成的。由于此方法把表上的關聯考慮進去了,也是一種比較的全面的考慮的一種清除方法,而且在清除過程中重建了表和索引,對于數據庫的存儲和性能上都有提高。因為這種方法一開始是rename表為臨時表,然后重建一個新表出來的,因此需要兩倍的表的空間,因此在操作之前一定要檢查要清除的表所在的表空間的free空間是否足夠;但是也有一定的缺陷,因為在新表中重新插入原來的數據后需要重建索引和限制,因此在時間和磁盤的空間上都有比較大的開銷,而且對于前臺的應用可能會有一段時間的中斷,當然,這個中斷時間就主要是消耗在重建索引和重建限制上了,而時間的長短跟需要重建索引和限制的多少以及表的記錄多少等等因素都有關系。使用這種方法對于7*24小時要求的系統上清除行遷移不是很合適,因為使用這種方法會導致系統可能有一段時間的停機,假如系統的實時性比較高,這種方法就不是很適用了。

 

  方法四:使用EXP/IMP工具清除行遷移的方法

1.    使用EXP導出存在有行遷移的表。


2.    然后TRUNCATE原來的表。

3.    IMP開始導出的表。

4.    重建表上所有的索引。(可選)

使用這種方法可以不用重建索引,省去了這部分時間,但是完成之后索引的使用效率不會很高,最好是在以后逐步的在線重建索引,這樣是可以不需要中斷業務的。但是需要考慮的是IMP的時候會比較慢,而且會占用比較大的IO,應該選擇在應用不是很繁忙的時候做這項工作,否則會對應用的正常運行產生較大的影響。對于這種方法還存在有一個比較大的弊端,就是在EXP表的時候要保證該表是沒有數據的更新或者是只讀狀態的,不能對表有插入或者更新操作,否則會導致數據的丟失。


SQL> select count(*) from test;

  COUNT(*)

----------

169344

SQL> truncate table chained_rows;

Table truncated.

SQL> analyze table test LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL> select count(*) from chained_rows;

  COUNT(*)

----------

      3294

$ exp allan/allan file=test.dmp tables=test

Export: Release 9.2.0.3.0 - Production on Sun Jun 6 13:50:08 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

Export done in ZHS16GBK character set and AL16UTF16 NCHAR character set

About to export specified tables via Conventional Path ...

. . exporting table                           TEST     169344 rows exported

Export terminated successfully without warnings.

$ sqlplus allan/allan

SQL*Plus: Release 9.2.0.3.0 - Production on Sun Jun 6 13:50:43 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved

Connected to:

Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

SQL> truncate table test;

Table truncated.

SQL> exit

Disconnected from Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

$ imp allan/allan file=test.dmp full=y ignore=y buffer=5000000

Import: Release 9.2.0.3.0 - Production on Sun Jun 6 13:51:24 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to: Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

Export file created by EXPORT:V09.02.00 via conventional path

import done in ZHS16GBK character set and AL16UTF16 NCHAR character set

. importing ALLAN's objects into ALLAN

. . importing table                         "TEST"     169344 rows imported


Import terminated successfully without warnings.

$ sqlplus allan/allan

SQL*Plus: Release 9.2.0.3.0 - Production on Sun Jun 6 13:52:53 2004

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

Connected to:

Oracle9i Enterprise Edition Release 9.2.0.3.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.3.0 - Production

SQL> select count(*) from test;

  COUNT(*)

----------

    169344

SQL> select index_name from user_indexes where table_name='TEST';       

INDEX_NAME

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

OBJ_INDEX

SQL> alter index OBJ_INDEX rebuild online;

Index altered.

SQL> truncate table chained_rows;

Table truncated.

SQL> analyze table test LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL> select count(*) from chained_rows;

  COUNT(*)

----------

         0


 

  方法五:使用MOVE命令來清除行遷移的方法

1.    查看要清除行遷移的表所在的表空間。 


Select table_name,tablespace_name from user_tables where table_name='table_name’;


2.    查看要清除行遷移的表上的具體索引。


select index_name,table_name from user_indexes where table_name=‘table_name’;


3.    Move要清除RM的表到指定的表空間中去。


alter table table_name move tablespace tablespace_name;


4.    重建表上的所有索引。


alter index index_name rebuild;


這種方法適用于8i及其以上的數據庫版本,主要是利用數據庫的一個MOVE命令來實現行遷移的清除的,MOVE命令的實質其實就是INSERT … SELECT的一個過程,在MOVE表的過程中是需要兩倍的原來的表大小的,因為中間過程是要保留原來的舊表的,新表創建完成后舊表就被刪除并釋放空間了。MOVE的時候要注重后面一定要加上表空間參數,所以必須要先知道表所在的表空間;因為MOVE表之后需要重建索引,所以之前要確定表上的所有的索引。

這種方法對于表記錄數很大或者表上索引太多的情況不太適用,因為本身的MOVE就會很慢, 而且MOVE表的時候會要鎖定表,時間長了會導致對表的其他操作出現問題,導致數據插入不了丟失數據;MOVE表后還要重建索引,索引太多了的話重建的時間也會太長;再者,這個方法也比較消耗資源,因此強烈建議在業務不繁忙的時候再執行。

以下是一個具體在生產數據庫上清除行遷移的例子,在這之前已經調整過表的pctfree參數至一個合適的值了:


SQL>ANALYZE TABLE SERVICE LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL>SELECT count(*) from chained_rows;

COUNT(*)

----------

    9145

SQL>select table_name,tablespace_name from user_tables where table_name='SERVICE';

TABLE_NAME                     TABLESPACE_NAME

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

SERVICE                        DATA

SQL>select index_name,table_name from user_indexes where table_name='SERVICE';

INDEX_NAME                     TABLE_NAME


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

I_SERVICE_ACCOUNTNUM           SERVICE

I_SERVICE_DATEACTIVATED        SERVICE

I_SERVICE_SC_S                 SERVICE

I_SERVICE_SERVICECODE          SERVICE

PK_SERVICE_SID                 SERVICE

SQL>select count(*) from SERVICE;

  COUNT(*)

----------

    518718

SQL>alter table SERVICE move tablespace DATA;

Table altered.

SQL>alter index I_SERVICE_ACCOUNTNUM rebuild;

Index altered.

SQL>alter index I_SERVICE_DATEACTIVATED rebuild;

Index altered.

SQL>alter index I_SERVICE_SC_S rebuild;

Index altered.

SQL>alter index I_SERVICE_SERVICECODE rebuild;

Index altered.

SQL>alter index PK_SERVICE_SID rebuild;

Index altered.

SQL>truncate table chained_rows;

Table truncated.

SQL>ANALYZE TABLE SERVICE LIST CHAINED ROWS INTO chained_rows;

Table analyzed.

SQL>SELECT count(*) from chained_rows;

COUNT(*)

----------

         0


利用MOVE命令來清除行遷移,執行的命令都相對比較的簡單,上面的例子中清除表SERVCIE中的行遷移的時間大概在五分鐘左右,其中move命令執行的時間為不到兩分鐘,也就是鎖表的時間大概是不到兩分鐘,對于大多數的應用來說一般問題都是不大的,放在系統閑的時候執行基本上不會對應用產生什么太多的影響。

 

  方法六:對于一些行遷移數量巨大而且表記錄數巨大的表的行遷移的清除方法

1.  使用TOAD工具或者別的方法獲取存在有大量行遷移并且表記錄很大的表的重建表的SQL,然后保存為腳本。

2.  使用RENAME命令將原始表重命名為一個備份表,然后刪除別的表對原始表上的限制、以及原始表上的外鍵和索引。

3.  利用1中生成的腳本重建原始表,以及表上的限制,外鍵,索引等對象。

4.  然后按表模式導出2中備份的表,然后導入到另外的一個臨時中轉的數據庫庫中,因為表的名字已經改變,所以導入后需要RENAME表為原來的名字,然后重新導出,最后再導入到原來的數據庫中。

這種方法主要是用來針對一些數據量比較大,并且表上的行遷移也比較多的表的行遷移清除。對于這些大表的行遷移的清除,正常來說都需要停應用一段較長時間才能夠清除掉,讓人感覺比較的頭疼,對于7*24小時的應用來說,down機的時間越長損失則越大,當然是要盡量的減短down機的時間。但是因為表本身比較大,不管怎樣做什么操作都是會比較耗費時間和資源的,但是假如應用在某段時間內主要是以插入數據為主,更新數據和刪除數據都很少的,因此可以考慮可以采用這么一種方法:先重命名表,然后重新建立一個和原來一樣的表,用來保證之后的應用的數據是可以正常插入的,從而使應用不用停很久,因為重建一個沒有任何數據的表結構的過程是很短暫的,大概需要幾秒鐘的時間,而重建好表了后就能保證應用能夠正常的寫入數據,從而使應用幾乎不用停頓,然后把開始重命名的原始表按表模式導出,因為表的名字已經被改變,因此需要一個臨時庫來導入這些數據,然后重命名回原來的名字,然后按原來的表名導出后再重新導入原始數據庫,這樣操作起來雖然會比較麻煩,但是卻是一種很有效很實際的方法,速度也很快,導出后導入,因為本身表結構已經建立好了,不需要其他任何的多的操作,而且最要害的是這種方法所需要的down機時間是最短的。


SQL>ALTER TABLE USER.PAY RENAME TO PAY_X ;

然后導出PAY_X表;

$ exp USER/USER file=PAY_X.dmp tables=PAY_X

SQL>ALTER TABLE USER.BATCHPAYMENTDETAIL DROP CONSTRAINT FK_BATCHPAYMENTDETAIL_OPAYID ;

SQL>ALTER TABLE USER.DEPOSITCLASSIFY DROP CONSTRAINT FK_DEPOSITCLASSIFY2 ;  

SQL>ALTER TABLE USER.DEPOSITCREDITLOG DROP CONSTRAINT FK_DEPOSITCREDITLOG2 ;     

SQL>ALTER TABLE USER.DEPOSIT DROP CONSTRAINT SYS_C003423 ;


SQL>ALTER TABLE USER.PAY_X DROP CONSTRAINT SYS_C003549 ;

SQL>DROP INDEX USER.I_PAY_STAFFID ;

SQL>CREATE TABLE USER.PAY

(

  PAYID           NUMBER(9),

  ACCOUNTNUM          NUMBER(9),

  TOTAL               NUMBER(12,2),

  PREVPAY         NUMBER(12,2),

  PAY             NUMBER(12,2),

  STAFFID             NUMBER(9),

  PROCESSDATE         DATE,

  PAYNO           CHAR(12),

  TYPE                CHAR(2)                   DEFAULT '0',

  PAYMENTMETHOD       CHAR(1)                   DEFAULT '0',

  PAYMENTMETHODID     VARCHAR2(20),

  BANKACCOUNT         VARCHAR2(32),

  PAYMENTID           NUMBER(9),

  STATUS              CHAR(1)                   DEFAULT '0',

  MEMO                VARCHAR2(255),

  SERVICEID           NUMBER(9),

  CURRENTDEPOSITID    NUMBER(9),

  SHOULDPROCESSDATE   DATE                      DEFAULT sysdate,

  ORIGINALEXPIREDATE  DATE,

  ORIGINALCANCELDATE  DATE,

  EXPIREDATE          DATE,

  CANCELDATE          DATE,

  DEPOSITTYPE         CHAR(1)

)

TABLESPACE USER

PCTUSED    95

PCTFREE    5

INITRANS   1

MAXTRANS   255

STORAGE    (

            INITIAL          7312K

            NEXT             80K


            MINEXTENTS       1

            MAXEXTENTS       2147483645

            PCTINCREASE      0

            FREELISTS        1

            FREELIST GROUPS  1

            BUFFER_POOL      DEFAULT

           )

NOLOGGING

NOCACHE

NOPARALLEL;

SQL>CREATE INDEX USER.I_PAY_STAFFID ON USER.PAY

(STAFFID)

NOLOGGING

TABLESPACE USER

PCTFREE    5

INITRANS   2

MAXTRANS   255

STORAGE    (

            INITIAL          1936K

            NEXT             80K

            MINEXTENTS       1

            MAXEXTENTS       2147483645

            PCTINCREASE      0

            FREELISTS        1

            FREELIST GROUPS  1

            BUFFER_POOL      DEFAULT

           )

NOPARALLEL;

SQL>CREATE UNIQUE INDEX USER.PK_PAY_ID ON USER.PAY

(PAYID)

NOLOGGING

TABLESPACE USER

PCTFREE    5

INITRANS   2

MAXTRANS   255

STORAGE    (

            INITIAL          1120K

            NEXT             80K

            MINEXTENTS       1

            MAXEXTENTS       2147483645


            PCTINCREASE      0

            FREELISTS        1

            FREELIST GROUPS  1

            BUFFER_POOL      DEFAULT

           )

NOPARALLEL;

SQL>ALTER TABLE USER.PAY ADD (

  FOREIGN KEY (STAFFID)

REFERENCES USER.STAFF (STAFFID));

SQL>ALTER TABLE USER.DEPOSITCLASSIFY ADD

 CONSTRAINT FK_DEPOSITCLASSIFY2

 FOREIGN KEY (PAYID)

  REFERENCES USER.PAY (PAYID)  ;  

SQL>ALTER TABLE USER.DEPOSITCREDITLOG ADD

 CONSTRAINT FK_DEPOSITCREDITLOG2

 FOREIGN KEY (PAYID)

  REFERENCES USER.PAY (PAYID)  ;  

SQL>ALTER FUNCTION "USER"."GENERATEPAYNO" COMPILE ; 

SQL>ALTER PROCEDURE "USER"."ENGENDERPRVPAY" COMPILE ;  

SQL>ALTER PROCEDURE "USER"."ISAP_ENGENDERPRVPAY" COMPILE ;  

SQL>ALTER PROCEDURE "USER"."SPADDCREDITDEPOSIT" COMPILE ;  

SQL>ALTER PROCEDURE "USER"."SPADDDEPOSITWITHOUTCARD" COMPILE ;  

SQL>ALTER PROCEDURE "USER"."SPADJUSTLWDEPOSIT" COMPILE ;  

……

然后將導出的表PAY_X的dmp文件導入一個臨時的數據庫中,然后在臨時數據庫中將其表名重新命名為PAY,再按表模式將其導出。

imp USER/USER file= PAY_x.dmp tables=PAY ignore=y

SQL>rename PAY_X to PAY;

$ exp USER/USER file=PAY.dmp tables=PAY

最后將這個dmp文件導入正式的生產數據庫中即可。


以上的過程在重建好PAY表后整個應用就恢復正常了,而重命名表后重建表的時間是非常之短的,我測試的時間大概是在幾分鐘之內就可以做完了,新數據就可以插入表了,剩下的工作就是將舊的數據導入數據庫,這個工作的時間要求上就沒有那么高了,因為應用已經正常了,一般來說利用晚上業務不忙的時候都可以把一張表的數據導入完成的。

  以上的六種清除行遷移的方法各有各自的優缺點,分別適用于不同的情況下使用,利用以上的幾種清除行遷移的方法基本上就能完全清除掉系統中的存在的行遷移了,當然,具體的生產環境中還需要具體問題具體分析的,針對不同類型的系統,系統中不同特點的表采用不同的清除方法,盡量的減少停數據庫的時間,以保證應用的不間斷穩定運行。

 

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美黄色片视频| 久久99热这里只有精品国产| 国产精品女主播| 日韩一区二区av| 日韩在线视频观看正片免费网站| 97精品国产aⅴ7777| 久久国产天堂福利天堂| 欧美黑人巨大精品一区二区| 6080yy精品一区二区三区| 中文字幕自拍vr一区二区三区| 久久精品影视伊人网| 亚洲欧美国产一区二区三区| 精品亚洲夜色av98在线观看| 青草青草久热精品视频在线观看| 68精品久久久久久欧美| 日本成人黄色片| 国产视频自拍一区| 日韩欧美国产免费播放| 亚洲精品视频二区| 久热99视频在线观看| 欧美激情在线播放| 国产精品日韩精品| 欧美另类xxx| 精品国产一区久久久| 亚洲一区二区久久久久久| 69视频在线免费观看| 亚洲精品aⅴ中文字幕乱码| 国产精品永久在线| 深夜精品寂寞黄网站在线观看| 亚洲精品国产综合久久| www.久久撸.com| 亚洲美女性生活视频| 成人免费视频在线观看超级碰| 精品二区三区线观看| 亚洲国产精品va在线| 欧美日韩国产中文字幕| 中文日韩在线视频| 国产精品爽黄69天堂a| 久热爱精品视频线路一| 欧美日韩国产精品一区| 亚洲乱码av中文一区二区| 欧美午夜片在线免费观看| 亚洲综合大片69999| 欧美激情a∨在线视频播放| 欧美高清无遮挡| 精品亚洲va在线va天堂资源站| 69视频在线播放| 57pao国产精品一区| 亚洲va久久久噜噜噜| 精品久久久久久久中文字幕| 欧美乱大交xxxxx| 欧美成人午夜激情在线| 日韩精品欧美国产精品忘忧草| 久久99亚洲热视| 成人久久精品视频| 欧美综合第一页| 国产亚洲视频在线观看| 国产精品欧美在线| 宅男66日本亚洲欧美视频| 久久国产精品首页| 91精品啪在线观看麻豆免费| 成人激情视频免费在线| 亚洲精品日韩久久久| 亚洲色图在线观看| 美女视频黄免费的亚洲男人天堂| 国产精品久久国产精品99gif| 国产精品视频在线观看| 亚洲第一网站免费视频| 中日韩美女免费视频网址在线观看| 欧美午夜丰满在线18影院| 欧美色道久久88综合亚洲精品| 97精品伊人久久久大香线蕉| 亚洲毛片一区二区| 日韩电影在线观看免费| 日韩av综合网| 中国china体内裑精亚洲片| 亚洲精品欧美日韩专区| 国语自产精品视频在线看抢先版图片| 欧美大片在线免费观看| 欧美激情一级欧美精品| 国产日韩精品综合网站| 国产91精品高潮白浆喷水| 欧美日韩精品中文字幕| 欧美高跟鞋交xxxxxhd| 2021国产精品视频| 亚洲资源在线看| 亚洲影影院av| 亚洲人成电影网站色xx| 亚洲激情中文字幕| 亚洲天堂久久av| 久久精品视频在线观看| 亚洲老头老太hd| 亚洲精品xxx| 亚洲天堂视频在线观看| 国模私拍一区二区三区| 国产精品偷伦免费视频观看的| 最近2019好看的中文字幕免费| 国模精品一区二区三区色天香| 国产精品福利在线| 成人免费福利在线| 日韩二区三区在线| 国语自产精品视频在线看抢先版图片| 国产精品视频男人的天堂| 在线看片第一页欧美| 欧美极品在线播放| 成人在线小视频| 色综合久久悠悠| 国产一区二区三区免费视频| 2024亚洲男人天堂| 久久综合伊人77777尤物| 国产精品久久久久久亚洲影视| 78m国产成人精品视频| 欧美激情二区三区| 亚洲专区中文字幕| 日本精品久久久久久久| 亚洲人成在线播放| 精品久久久久久久久久| 欧美日韩免费看| 成人有码在线视频| 在线观看欧美成人| 久久精品视频一| 久久国内精品一国内精品| 亚洲一区二区三区乱码aⅴ| 色无极影院亚洲| 国产精品pans私拍| 欧美高清激情视频| 亚洲福利精品在线| 91在线播放国产| 理论片在线不卡免费观看| 欧美激情综合色| 久久视频精品在线| 亚洲一区二区三区成人在线视频精品| 欧美视频在线观看 亚洲欧| 欧美一级电影免费在线观看| 亚洲黄色www网站| 国产美女直播视频一区| 美女啪啪无遮挡免费久久网站| 亚州欧美日韩中文视频| 成人中心免费视频| 亚洲美女久久久| 91精品91久久久久久| 韩日欧美一区二区| 精品视频在线播放色网色视频| 亚洲精品美女在线观看| 91在线免费看网站| 日韩精品有码在线观看| 91久久久久久久久久| 欧美在线激情视频| 68精品久久久久久欧美| 亚洲男人av电影| 91av福利视频| 成人免费视频在线观看超级碰| 91午夜在线播放| 久久国产精品偷| 国产福利视频一区二区| 国产精品444| 97视频免费看| 亚洲综合国产精品| 国产一区视频在线| 第一福利永久视频精品| 96sao精品视频在线观看| 九九热精品视频在线播放| 成人信息集中地欧美| 97香蕉超级碰碰久久免费的优势|