一
在實際的工作中我們經常會碰到一些Oracle數據庫性能較低的問題
那究竟什麼是行遷移/行鏈接呢
操作系統的最小讀寫操作單元是操作系統的block
一個Oracle block由三個部分組成
數據塊頭
自由空間
實際數據
當創建或者更改任何表和索引的時候
PCTFREE:為將來更新已經存在的數據預留空間的百分比
PCTUSED:用於為插入一新行數據的最小空間的百分比
當表中一行的數據不能在一個數據block中放入的時候
行鏈接產生在第一次插入數據的時候如果一個block不能存放一行記錄的情況下
當一行記錄初始插入的時候事可以存儲在一個block中的
當發生了行遷移或者行鏈接
下面舉例來具體說明行遷移/行鏈接的產生過程
先創建一個pctfree為
create table test(
col
col
storage (
pctfree
pctused
當插入一條記錄的時候
Oracle使用free list機制可以大大的提高性能
具體我們通過下面的一個試驗來查看行鏈接和行遷移是如何產生並在數據文件中體現出來的
先查看ALLAN這個表空間的數據文件號
SQL> select file_id from dba_data_files where tablespace_name=
FILE_ID
創建一個測試表test
SQL> create table test ( x int primary key
Table created
因為我的數據庫的db_block_size是
然後插入一行記錄
SQL> insert into test(x) values (
SQL> commit;
Commit complete
查找這行記錄所在的block
SQL> select dbms_rowid
DBMS_ROWID
SQL> alter system dump datafile
System altered
在udump目錄下查看trace文件的內容如下
Start dump data blocks tsn:
buffer tsn:
scn:
frmt:
Block header dump:
Object id on Block? Y
seg/obj:
fsl:
Itl Xid Uba Flag Lck Scn/Fsc
data_block_dump
===============
tsiz:
hsiz:
pbl:
bdba:
flag=
ntab=
nrow=
frre=
fsbo=
fseo=
avsp=
tosp=
block_row_dump:
tab
tl:
col
end_of_block_dump
End dump data blocks tsn:
對其中的一些信息做一些解釋
Fb:H是指行記錄的頭
Cc
Nrid
由上面的dump信息我們可以看出來當前表test是沒有行鏈接或者行遷移的
然後更新test表
SQL> update test set a=
SQL> commit;
Commit complete
此時應該有行遷移/行鏈接產生了
SQL> alter system dump datafile
System altered
在udump目錄下查看trace文件的內容如下
Start dump data blocks tsn:
buffer tsn:
scn:
frmt:
Block header dump:
Object id on Block? Y
seg/obj:
fsl:
Itl Xid Uba Flag Lck Scn/Fsc
data_block_dump
===============
tsiz:
hsiz:
pbl:
bdba:
flag=
ntab=
nrow=
frre=
fsbo=
fseo=
avsp=
tosp=
block_row_dump:
tab
tl:
nrid:
col
col
…………
col
end_of_block_dump
End dump data blocks tsn:
我們不難看出
二
通過前面的介紹我們知道
如何檢測數據庫中存在有了行遷移和行鏈接呢?我們可以利用Oracle數據庫自身提供的腳本utlchain
SPOOL list_migation_rows
SET ECHO OFF
SET HEADING OFF
SELECT
SPOOL OFF
然後查詢chained_rows表
SELECT table_name
當然
SELECT name
可以使用如下的腳本來直接查找存在有行鏈接和行遷移的表
accept owner prompt
prompt
prompt
accept table prompt
prompt
prompt
set head off serverout on term on feed off veri off echo off
!clear
prompt
declare
v_owner varchar
v_table varchar
v_chains number;
v_rows number;
v_count number :=
sql_stmt varchar
dynamicCursor INTEGER;
dummy INTEGER;
cursor chains is
select count(*) from chained_rows;
cursor analyze is
select owner
from sys
where owner like upper(
and table_name like upper(
order by table_name;
begin
dbms_output
open analyze;
fetch analyze into v_owner
while analyze%FOUND loop
dynamicCursor := dbms_sql
sql_stmt :=
dbms_sql
dummy := dbms_sql
dbms_sql
open chains;
fetch chains into v_chains;
if (v_chains !=
if (v_count =
dbms_output
v_count :=
end if;
dynamicCursor := dbms_sql
sql_stmt :=
dbms_sql
dbms_sql
dummy := dbms_sql
dummy := dbms_sql
dbms_sql
dbms_sql
dbms_output
dbms_output
dynamicCursor := dbms_sql
sql_stmt :=
dbms_sql
dummy := dbms_sql
dbms_sql
v_chains :=
end if;
close chains;
fetch analyze into v_owner
end loop;
if (v_count =
dbms_output
end if;
close analyze;
end;
/
set feed on head on
prompt
三
由於對於行鏈接來說只能增大db_block_size來清除
對於行遷移的清除
眾所周知
第一種是定量的的設定方法
第二種是差分微調的方法
使用上述的方法控制住了當前表的行遷移的增長之後
方法一
具體步驟如下
@$ORACLE_HOME/rdbms/admin/utlchain
ANALYZE TABLE table_name LIST CHAINED ROWS INTO chained_rows;
CREATE TABLE table_name_temp AS
SELECT * FROM table_name
WHERE rowid IN
(SELECT head_rowid FROM chained_rows
WHERE table_name =
DELETE table_name
WHERE rowid IN
(SELECT head_rowid
FROM chained_rows
WHERE table_name =
INSERT INTO table_name SELECT * FROM table_name_temp;
DROP TABLE table_name_temp;
對於這種傳統的清除RM的方法
以下是一個具體在生產數據庫上清除行遷移的例子
SQL>@$ORACLE_HOME/rdbms/admin/utlchain
Table created
SQL> ANALYZE TABLE CUSTOMER LIST CHAINED ROWS INTO chained_rows;
Table analyzed
SQL>SELECT count(*) from chained_rows
TABLE_NAME COUNT(*)
CUSTOMER
查看在CUSTOMER表上存在的限制:
SQL>select CONSTRAINT_NAME
CONSTRAINT_NAME C TABLE_NAME
PK_CUSTOMER
SQL>select CONSTRAINT_NAME
no rows selected
SQL> CREATE TABLE CUSTOMER_temp AS
SELECT * FROM CUSTOMER WHERE rowid IN
(SELECT head_rowid FROM chained_rows
WHERE table_name =
Table created
SQL>select count(*) from CUSTOMER;
COUNT(*)
SQL> DELETE CUSTOMER WHERE rowid IN
(SELECT head_rowid
FROM chained_rows
WHERE table_name =
SQL> INSERT INTO CUSTOMER SELECT * FROM CUSTOMER_temp;
SQL> DROP TABLE CUSTOMER_temp;
Table dropped
SQL> commit;
Commit complete
SQL> select count(*) from CUSTOMER;
COUNT(*)
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(*)
以上整個清除兩萬多行的行遷移過程在三分鐘左右
方法二
這種算法是對傳統算法的一種改進
以下是一個具體在生產數據庫上清除行遷移的例子
SQL>select index_name
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_NAME C TABLE_NAME
SYS_C
SQL>alter table CONN disable constraint SYS_C
Table altered
SQL>CREATE TABLE TERMINAL_temp AS
SELECT * FROM TERMINAL
WHERE rowid IN
(SELECT head_rowid FROM chained_rows
WHERE table_name =
Table created
SQL>select count(*) from TERMINAL_temp;
COUNT(*)
SQL>DELETE TERMINAL
WHERE rowid IN
(SELECT head_rowid
FROM chained_rows
WHERE table_name =
SQL>INSERT INTO TERMINAL SELECT * FROM TERMINAL_temp;
SQL>alter table CONN disable constraint SYS_C
Table altered
SQL>select count(*) from terminal;
COUNT(*)
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(*)
從上面過程中可以看出
方法三
RENAME table_name TO table_name_temp;
SELECT CONSTRAINT_NAME
ALTER TABLE table_name DROP CONSTRAINT XXXX;(XXXX為上述的查詢結果)
CREATE TABLE table_name AS SELECT * FROM table_name_temp WHERE
INSERT /*+ APPEND */ INTO table_name SELECT * FROM table_name_temp;
對於使用這種方法來清除行遷移
方法四
使用這種方法可以不用重建索引
SQL> select count(*) from test;
COUNT(*)
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(*)
$ exp allan/allan file=test
Export: Release
Copyright (c)
Connected to: Oracle
With the Partitioning
JServer Release
Export done in ZHS
About to export specified tables via Conventional Path
Export terminated successfully without warnings
$ sqlplus allan/allan
SQL*Plus: Release
Copyright (c)
Connected to:
Oracle
With the Partitioning
JServer Release
SQL> truncate table test;
Table truncated
SQL> exit
Disconnected from Oracle
With the Partitioning
JServer Release
$ imp allan/allan file=test
Import: Release
Copyright (c)
Connected to: Oracle
With the Partitioning
JServer Release
Export file created by EXPORT:V
import done in ZHS
Import terminated successfully without warnings
$ sqlplus allan/allan
SQL*Plus: Release
Copyright (c)
Connected to:
Oracle
With the Partitioning
JServer Release
SQL> select count(*) from test;
COUNT(*)
SQL> select index_name from user_indexes where table_name=
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(*)
方法五
Select table_name
select index_name
alter table table_name move tablespace tablespace_name;
alter index index_name rebuild;
這種方法適用於
這種方法對於表記錄數很大或者表上索引太多的情況不太適用
以下是一個具體在生產數據庫上清除行遷移的例子
SQL>ANALYZE TABLE SERVICE LIST CHAINED ROWS INTO chained_rows;
Table analyzed
SQL>SELECT count(*) from chained_rows;
COUNT(*)
SQL>select table_name
TABLE_NAME TABLESPACE_NAME
SERVICE DATA
SQL>select index_name
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(*)
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(*)
利用MOVE命令來清除行遷移
方法六
這種方法主要是用來針對一些數據量比較大
SQL>ALTER TABLE USER
然後導出PAY_X表;
$ exp USER/USER file=PAY_X
SQL>ALTER TABLE USER
SQL>ALTER TABLE USER
SQL>ALTER TABLE USER
SQL>ALTER TABLE USER
SQL>ALTER TABLE USER
SQL>DROP INDEX USER
SQL>CREATE TABLE USER
(
PAYID NUMBER(
ACCOUNTNUM NUMBER(
TOTAL NUMBER(
PREVPAY NUMBER(
PAY NUMBER(
STAFFID NUMBER(
PROCESSDATE DATE
PAYNO CHAR(
TYPE CHAR(
PAYMENTMETHOD CHAR(
PAYMENTMETHODID VARCHAR
BANKACCOUNT VARCHAR
PAYMENTID NUMBER(
STATUS CHAR(
MEMO VARCHAR
SERVICEID NUMBER(
CURRENTDEPOSITID NUMBER(
SHOULDPROCESSDATE DATE DEFAULT sysdate
ORIGINALEXPIREDATE DATE
ORIGINALCANCELDATE DATE
EXPIREDATE DATE
CANCELDATE DATE
DEPOSITTYPE CHAR(
)
TABLESPACE USER
PCTUSED
PCTFREE
INITRANS
MAXTRANS
STORAGE (
INITIAL
NEXT
MINEXTENTS
MAXEXTENTS
PCTINCREASE
FREELISTS
FREELIST GROUPS
BUFFER_POOL DEFAULT
)
NOLOGGING
NOCACHE
NOPARALLEL;
SQL>CREATE INDEX USER
(STAFFID)
NOLOGGING
TABLESPACE USER
PCTFREE
INITRANS
MAXTRANS
STORAGE (
INITIAL
NEXT
MINEXTENTS
MAXEXTENTS
PCTINCREASE
FREELISTS
FREELIST GROUPS
BUFFER_POOL DEFAULT
)
NOPARALLEL;
SQL>CREATE UNIQUE INDEX USER
(PAYID)
NOLOGGING
TABLESPACE USER
PCTFREE
INITRANS
MAXTRANS
STORAGE (
INITIAL
NEXT
MINEXTENTS
MAXEXTENTS
PCTINCREASE
FREELISTS
FREELIST GROUPS
BUFFER_POOL DEFAULT
)
NOPARALLEL;
SQL>ALTER TABLE USER
FOREIGN KEY (STAFFID)
REFERENCES USER
SQL>ALTER TABLE USER
CONSTRAINT FK_DEPOSITCLASSIFY
FOREIGN KEY (PAYID)
REFERENCES USER
SQL>ALTER TABLE USER
CONSTRAINT FK_DEPOSITCREDITLOG
FOREIGN KEY (PAYID)
REFERENCES USER
SQL>ALTER FUNCTION
SQL>ALTER PROCEDURE
SQL>ALTER PROCEDURE
SQL>ALTER PROCEDURE
SQL>ALTER PROCEDURE
SQL>ALTER PROCEDURE
……
然後將導出的表PAY_X的dmp文件導入一個臨時的數據庫中
imp USER/USER file= PAY_x
SQL>rename PAY_X to PAY;
$ exp USER/USER file=PAY
最後將這個dmp文件導入正式的生產數據庫中即可
以上的過程在重建好PAY表後整個應用就恢復正常了
以上的六種清除行遷移的方法各有各自的優缺點
From:http://tw.wingwit.com/Article/program/Oracle/201311/18114.html