现在的位置: 首页 > 综合 > 正文

行选移与行链接的介绍

2013年06月26日 ⁄ 综合 ⁄ 共 17833字 ⁄ 字号 评论关闭

一、                 行迁移/行链接的简介

在实际的工作中我们经常会碰到一些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的结构如下图所示:

 

blockheader.png

                               图一: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,如下图所示:

blockheader2.png

图二:Oracle空的block结构图

假设插入第一条记录的时候占用一个block的10%的空间(除去block头占去的大小),剩余的自由空间90%大于pctfree20%,因此这个block还将继续为下次的插入操作提供空间。

blockheader3.png

再连续插入七条记录,使block的剩余自由空间剩下20%,此时,这个block将要从free list中移走,如果再插入记录,Oracle将再free list中寻找下一个空余的block去存放后来插入的数据。

blockheader4.png

图四:插入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

抱歉!评论已关闭.