网上搜集的,待整理
pctused(percent used)与pctfree(percent free)是Oracle的两个与性能相关的块级存储参数。虽然我很少修改它们,但是相应的概念还是比较重要的,所以强化一下。
概念:
pctused:一个块的使用水位的百分比,这个水位将使该块返回到可用列表中去等待更多的插入操作。
pctfree:用来为一个块保留的空间百分比,以防止在今后的更新操作中增加一列或多列值的长度。
freelist:可用列表是表中的一组可插入数据的可用块。
行连接:指一行存储在多个块中的情况,这是因为该行的长度超过了一个块的可用空间大小,即行链接是跨越多块的行。
行迁移:指一个数据行不适合放入当前块而被重新定位到另一个块(那里有充足的空间)中,但在原始块中保留一个指针的情形。原始块中的指针是必需的,因为索引的ROWID项仍然指向原始位置。
计算公式:
PCTFREE=(Average Row Size-Initial Row Size)*100/Average Row Size
PCTUSED=(100-PCTFREE) -Average Row Size * 100/Availabe Data Space
Oracle的其中一个优点时它可以管理每个表空间中的自由空间。Oracle负责处理表和索引的空间管理,这样就可以让我们无需懂得Oracle的表和索引的内部运作。不过,对于有经验的Oracle调优专家来说,他需要懂得Oracle是如何管理表的extent和空闲的数据块。对于调整拥有高的 insert或者update的系统来说,这是非常重要的。
要精通对象的调整,你需要懂得freelists和freelist组的行为,它们和pctfree及pctused参数的值有关。这些知识对于企业资源计划(ERP)的应用是特别重要的,因为在这些应用中,不正确的表设置通常是DML语句执行慢的原因。
对于初学者来说,最常见的错误是认为默认的Oracle参数对于所有的对象都是最佳的。除非磁盘的消耗不是一个问题,否则在设置表的pctfree和 pctused参数时,就必须考虑平均的行长和数据库的块大小,这样空的块才会被有效地放到freelists中。当这些设置不正确时,那些得到的 freelists也是"dead"块,因为它们没有足够的空间来存储一行,这样将会导致明显的处理延迟。
Freelists对于有效地重新使用Oracle表空间中的空间是很重要的,它和pctfree及pctused这两个存储参数的设置直接相关。如果将 pctused设置为一个高的值,这时数据库就会尽快地重新使用块。不过,高性能和有效地重新使用表的块是对立的。在调整Oracle的表格和索引时,需要认真考虑究竟需要高性能还是有效的空间重用,并且据此来设置表的参数。以下我们来看一下这些freelists是如何影响Oracle的性能的。
当有一个请求需要插入一行到表格中时,Oracle就会到freelist中寻找一个有足够的空间来容纳一行的块。你也许知道,freelist串是放在表格或者索引的第一个块中,这个块也被称为段头(segment header)。pctfree和pctused 参数的唯一目的就是为了控制块如何在freelists中进出。虽然freelist link和 unlink是简单的Oracle功能,不过设置freelist link (pctused) 和unlink (pctfree) 对Oracle的性能确实有影响。
由DBA的基本知识知道,pctfree参数是控制freelist un-links的(即将块由freelists中移除)。设置pctfree=10 意味着每个块都保留10%的空间用作行扩展。pctused参数是控制freelist re-links的。设置pctused=40意味着只有在块的使用低于40%时才会回到表格的freelists中。
许多新手对于一个块重新回到freelists后的处理都有些误解。其实,一旦由于一个删除的操作而令块被重新加入到freelist中,它将会一直保留在freelist中即使空间的使用超过了60%,只有在到达pctfree时才会将数据块由freelist中移走。
表格和索引存储参数设置的要求总结
以下的一些规则是用来设置freelists, freelist groups, pctfree和pctused存储参数的。你也知道,pctused和pctfree的值是可以很容易地通过alter table命令修改的,一个好的DBA应该知道如何设置这些参数的最佳值。
有效地使用空间和高性能之间是有矛盾的,而表格的存储参数就是控制这个方面的矛盾:
. 对于需要有效地重新使用空间,可以设置一个高的pctused值,不过副作用是需要额外的I/O。一个高的pctused值意味着相对满的块都会放到 freelist中。因此,这些块在再次满之前只可以接受几行记录,从而导致更多的I/O。
. 追求高性能的话,可以将pctused设置为一个低的值,这意味着Oracle不会将数据块放到freelists中直到它几乎是空的。那么块将可以在满之前接收更多的行,因此可以减少插入操作的I/O。要记住Oracle扩展新块的性能要比重新使用现有的块高。对于Oracle来说,扩展一个表比管理 freelists消耗更少的资源。
让我们来回顾一下设置对象存储参数的一些常见规则:
.经常将pctused设置为可以接收一条新行。对于不能接受一行的free blocks对于我们来说是没有用的。如果这样做,将会令Oracle的性能变慢,因为Oracle将在扩展表来得到一个空的块之前,企图读取5 个"dead"的free block。
.表格中chained rows的出现意味着pctfree太低或者是db_block_size太少。在很多情况下,RAW和LONG RAW列都很巨大,以至超过了Oracle的最大块的大小,这时chained rows是不可以避免的。
.如果一个表有同时插入的SQL语句,那么它需要有同时删除的语句。运行单一个一个清除的工作将会把全部的空闲块放到一个freelist中,而没有其它包含有任何空闲块的freelists出现。
.freelist参数应该设置为表格同时更新的最大值。例如,如果在任何时候,某个表最多有20个用户执行插入的操作,那么该表的参数应该设置为 freelists=20。
应记住的是freelist groups参数的值只是对于Oracle Parallel Server和Real Application Clusters才是有用的。对于这类Oracle,freelist groups应该设置为访问该表格的Oracle Parallel Server实例的数目。
一、行迁移/行链接的介绍
Row Migration (行迁移) & Row Chaining (行链接)就是其中我们可以尽量避免的引起Oracle数据库性能低下的潜在问题。通过合理的诊断行迁移/行链接,我们可以较大幅度上提高Oracle数据库的性能。
什么是行迁移/行链接
操作系统的最小读写操作单元是操作系统的block,所以当创建一个Oracle数据库的时候我们应该讲数据库的block size设置成为操作系统的block size的整数倍,Oracle block是Oracle数据库中读写操作的最小单元,Oracle9i之前的Oracle数据库版本中Oracle block一旦在创建数据库的时候被设定后就没法再更改。为了在创建数据库之前确定一个合理的Oracle block的大小,我们需要考虑一些因素,例如数据库本身的大小以及并发事务的数量等。使用一个合适的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来获得这行的信息。
二、行迁移/行链接的检测
行链接主要是由于数据库的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';
可以使用如下的脚本来直接查找存在有行链接和行迁移的表,自动完成所有的分析和统计。
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;
三、行迁移和行链接的清除
由 于对于行链接来说只能增大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;
6.查看在CUSTOMER表上存在的限制
select CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where R_CONSTRAINT_NAME='PK_CUSTOMER1';
对于这种传统的清除RM的方法,优点是执行起来过程比较简单,容易实现。但是这种算法的缺陷是没有考虑到表关联的情况,在大多数数据库中很多表都是和别的表之间有表关联的,有外键的限制,这样就造成在步骤3中根本无法delete掉存在有行迁移的记录行,所以这种方法能够适用的表的范围是有限的,只能适用于表上无任何外键关联的表。由于这种方法在插入和删除数据的时候都没有disable掉索引,这样导致主要消耗时间是在删除和插入时维持索引树的均衡上了,这个对于如果记录数不多的情况时间上还比较短,但是如果对于记录数很多的表这个所消耗的时间就不是能够接受的。显然,这种方法在处理大数据量的表的时候显然是不可取的。
方法二:改进了的传统清除行迁移的方法
1. 执行$ORACLE_HOME/rdbms/admin目录下的utlchain.sql脚本创建chained_rows表。
@$ORACLE_HOME/rdbms/admin/utlchain.sql
2. 禁用所有其它表上关联到此表上的所有限制。
select index_name,index_type,table_name from user_indexes where table_name='MTL_SYSTEM_ITEMS_B';
select CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME from USER_CONSTRAINTS where table_name = 'MTL_SYSTEM_ITEMS_B';
alter table table_name disable constraint constraint_name;
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;
6. 启用所有其它表上关联到此表上的所有限制。
alter table table_name enable constraint constraint_name;
这种算法是对传统算法的一种改进,对于使用这种算法来清除行迁移,考虑到了表之间的关联,还可以灵活的利用的TOAD工具生成的表关联信息,是一种比较适合于清除行迁移的一种方法。但是因为使用这种方法后来需要重建索引,如果记录数很大,比如说上千万条以上的记录的表,就不是很合适,因为这个重建索引的时间会很长,是线性时间复杂度的,而重建索引是会导致索引所在的表被锁定的,从而导致插入不了新的记录,重建索引的时间太长导致记录长时间插入不了是会严重影响应用的,甚至导致数据的丢失,因此这个是使用这个方法前必须要考虑到的一个重要因素;对于8i以上的版本可以使用online的方法来重建索引,这样不会导致锁表,但是会有额外更多的开销,时间会很长。再者,因为这种方法在插入记录和删除记录都是带着索引的,如果表上的行迁移比较多,这样耗时间会比较长,而且占用资源也会比较大,因此只适用于表上行迁移存在的比较少的表。总的来说,这种方法对于表记录太多或者是表上的行迁移太多的情况都不是很适用,比较适合表记录少和表上行迁移都不太多的情况。
方法三:使用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 constraint_name;
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导出存在有行迁移的表。
select count(*) from table_name;
truncate table chained_rows;
analyze table table_name LIST CHAINED ROWS INTO chained_rows;
select count(*) from chained_rows;
$ exp allan/allan file=test.dmp tables=test
$ sqlplus allan/allan
2. 然后TRUNCATE原来的表。
truncate table test;
3. IMP开始导出的表。
imp allan/allan file=test.dmp full=y ignore=y buffer=5000000
select count(*) from table_name;
4. 重建表上所有的索引。(可选)
select index_name from user_indexes where table_name='TEST';
alter index OBJ_INDEX rebuild online;
truncate table chained_rows;
analyze table test LIST CHAINED ROWS INTO chained_rows;
select count(*) from chained_rows;
使用这种方法可以不用重建索引,省去了这部分时间,但是完成之后索引的使用效率不会很高,最好是在以后逐步的在线重建索引,这样是可以不需要中断业务的。但是需要考虑的是IMP的时候会比较慢,而且会占用比较大的IO,应该选择在应用不是很繁忙的时候做这项工作,否则会对应用的正常运行产生较大的影响。对于这种方法还存在有一个比较大的弊端,就是在EXP表的时候要保证该表是没有数据的更新或者是只读状态的,不能对表有插入或者更新操作,否则会导致数据的丢失。
方法五:使用MOVE命令来清除行迁移的方法
1. 查看要清除行迁移的表所在的表空间。
ANALYZE TABLE table_name LIST CHAINED ROWS INTO chained_rows;
SELECT count(*) from chained_rows;
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';
select count(*) from table_name;
3. Move要清除RM的表到指定的表空间中去。
alter table table_name move tablespace tablespace_name;
4. 重建表上的所有索引。
alter index index_name rebuild;-----
truncate table chained_rows;
ANALYZE TABLE table_name LIST CHAINED ROWS INTO chained_rows;
SELECT count(*) from chained_rows;
这种方法适用于8i及其以上的数据库版本,主要是利用数据库的一个MOVE命令来实现行迁移的清除的,MOVE命令的实质其实就是INSERT … SELECT的一个过程,在MOVE表的过程中是需要两倍的原来的表大小的,因为中间过程是要保留原来的旧表的,新表创建完成后旧表就被删除并释放空间了。MOVE的时候要注意后面一定要加上表空间参数,所以必须要先知道表所在的表空间;因为MOVE表之后需要重建索引,所以之前要确定表上的所有的索引。
这种方法对于表记录数很大或者表上索引太多的情况不太适用,因为本身的MOVE就会很慢, 而且MOVE表的时候会要锁定表,时间长了会导致对表的其他操作出现问题,导致数据插入不了丢失数据;MOVE表后还要重建索引,索引太多了的话重建的时间也会太长;再者,这个方法也比较消耗资源,因此强烈建议在业务不繁忙的时候再执行。
方法六:对于一些行迁移数量巨大而且表记录数巨大的表的行迁移的清除方法
1. 使用TOAD工具或者别的方法获取存在有大量行迁移并且表记录很大的表的重建表的SQL,然后保存为脚本。
2. 使用RENAME命令将原始表重命名为一个备份表,然后删除别的表对原始表上的限制、以及原始表上的外键和索引。
3. 利用1中生成的脚本重建原始表,以及表上的限制,外键,索引等对象。
4. 然后按表模式导出2中备份的表,然后导入到另外的一个临时中转的数据库库中,因为表的名字已经改变,所以导入后需要RENAME表为原来的名字,然后重新导出,最后再导入到原来的数据库中。
这种方法主要是用来针对一些数据量比较大,并且表上的行迁移也比较多的表的行迁移清除。对于这些大表的行迁移的清除,正常来说都需要停应用一段较长时间才能够清除掉,让人感觉比较的头疼,对于7*24小时的应用来说,down机的时间越长损失则越大,当然是要尽量的减短down机的时间。但是因为表本身比较大,不管怎样做什么操作都是会比较耗费时间和资源的,但是如果应用在某段时间内主要是以插入数据为主,更新数据和删除数据都很少的,因此可以考虑可以采用这么一种方法:先重命名表,然后重新建立一个和原来一样的表,用来保证之后的应用的数据是可以正常插入的,从而使应用不用停很久,因为重建一个没有任何数据的表结构的过程是很短暂的,大概需要几秒钟的时间,而重建好表了后就能保证应用能够正常的写入数据,从而使应用几乎不用停顿,然后把开始重命名的原始表按表模式导出,因为表的名字已经被改变,因此需要一个临时库来导入这些数据,然后重命名回原来的名字,然后按原来的表名导出后再重新导入原始数据库,这样操作起来虽然会比较麻烦,但是却是一种很有效很实际的方法,速度也很快,导出后导入,因为本身表结构已经建立好了,不需要其他任何的多的操作,而且最关键的是这种方法所需要的down机时间是最短的。
Eg:
ALTER TABLE USER.PAY RENAME TO PAY_X ;
然后导出PAY_X表;
$ exp USER/USER file=PAY_X.dmp tables=PAY_X;
ALTER TABLE USER.BATCHPAYMENTDETAIL DROP CONSTRAINT FK_BATCHPAYMENTDETAIL_OPAYID ;
ALTER TABLE USER.DEPOSITCLASSIFY DROP CONSTRAINT FK_DEPOSITCLASSIFY2 ;
ALTER TABLE USER.DEPOSITCREDITLOG DROP CONSTRAINT FK_DEPOSITCREDITLOG2 ;
ALTER TABLE USER.DEPOSIT DROP CONSTRAINT SYS_C003423 ;
ALTER TABLE USER.PAY_X DROP CONSTRAINT SYS_C003549 ;
DROP INDEX USER.I_PAY_STAFFID ;
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;
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;
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;
ALTER TABLE USER.PAY ADD ( FOREIGN KEY (STAFFID)REFERENCES USER.STAFF (STAFFID));
ALTER TABLE USER.DEPOSITCLASSIFY ADD CONSTRAINT FK_DEPOSITCLASSIFY2 FOREIGN KEY (PAYID) REFERENCES USER.PAY (PAYID) ;
ALTER TABLE USER.DEPOSITCREDITLOG ADD CONSTRAINT FK_DEPOSITCREDITLOG2 FOREIGN KEY (PAYID) REFERENCES USER.PAY (PAYID) ;
ALTER FUNCTION "USER"."GENERATEPAYNO" COMPILE ;
ALTER PROCEDURE "USER"."ENGENDERPRVPAY" COMPILE ;
ALTER PROCEDURE "USER"."ISAP_ENGENDERPRVPAY" COMPILE ;
ALTER PROCEDURE "USER"."SPADDCREDITDEPOSIT" COMPILE ;
ALTER PROCEDURE "USER"."SPADDDEPOSITWITHOUTCARD" COMPILE ;
ALTER PROCEDURE "USER"."SPADJUSTLWDEPOSIT" COMPILE ;
imp USER/USER file= PAY_x.dmp tables=PAY ignore=y
rename PAY_X to PAY;
exp USER/USER file=PAY.dmp tables=PAY
转载:http://blog.csdn.net/rainnyzhong/archive/2010/03/12/5374588.aspx
美国PVH DB存在行链接.通过我的检查,发现,我们的DB同样存在行迁移和行链接.ORACLE的dbms_stats.gather_schema_stats只会收集优化器统计信息,不会检测表的记录是否存在行迁移和行链接.(我原先被这个包给骗了,所以执行完dbms_stats.gather_schema_stats后一查数据字典:USER_TABLES,其中的字段chain_cnt值全为0).要检测表的记录是否存在row chain,需要用到ORACLE早先一点的命令:ANALYZE TABLE.所以我写了一个存储过程(P_analyze_schema)以检测DB:PVH91USRD中所有表存在行链接的情况.
值得注意的是:数据字典 USER_TABLES.CHAIN_CNT的值是包含了行迁移和行链接的数目.请看ORACLE的解释:
CHAIN_CNT* Number of rows in the table that are chained from one data block to another, or which have migrated to a new block, requiring a link to preserve the old ROWID
也就是说这个字段的值是行迁移和行链接的总数量,至于要知道具体行迁移有多少,行链接又是多少,ANALYZE TABLE这个命令没办法得到,只有通过DUMP BLOCK来区分(方法复杂,故没有去做).但首先我们要知道行迁移和行链接是两个不同的概念:
(1)行迁移: ORACLE一个BLOCK的DEFAULT SIZE是8K,事实上,一个BLOCK不可以存储8K的数据.一个BLOCK可以存储多少数据,由PCTFREE,PCTUSED参数控制(对于以前的手工管理的表空间而言).
PCTFREE:是指BLOCK保留空闲空间的百分比,用于UPDATE。对于已经插入到BLOCK的行而言,后面的UPDATE操作有可能使行的长度增加,PCTFREE就是用于容纳增加的那部分长度而保留的空闲空间。如果UPDATE时PCTFREE再也不能够容纳行增加的长度,则ORACLE会将整个行迁移到一个新的BLOCK,行的ROWID保留(不是太明白为什么ORACLE不改变ROWID),原来的BLOCK有一个指针指向ROW存放的新BLOCK。这就是行迁移。可见,行迁移是由于UPDATE操作所导致。从字面上理解,所谓迁移,肯定先有存在这一行,才能叫着迁移.
PCTUSED:是指BLOCK用于INSERT的百分比。对于INSERT操作,BLOCK可用于容纳新行的最大空间为Blocksize-pctfree-overhead.当BLOCK数据存储已高于PCTUSED,ORACLE会将该块从自由链表中移除,直到该块已使用空间降到PCTUSED以下,才会再次将此块重新加入到Freelist(这是ORACLE以前手工管理的表空间管理空闲块的原理,现在ORACLE推荐使用ASSM).
行迁移和行链接的检测:
除了我写的存储过程可以检测以外,一个简单的检测方法是:
select b.NAME,a.VALUE from v$mystat a,v$statname b
where a.STATISTIC#=b.STATISTIC#
and b.NAME like 'table fetch continued row'
当有返回值时,可以知道表的数据存在行迁移和行链接。
行迁移和行链接的清除:
能过REBUILD数据来清除行迁移:
create table MM_PM_temp as select * from MM_PM;
truncate table MM_PM;
insert into MM_PM select * from MM_PM_temp
再重新分析表:
analyze table MM_PM compute statistics;
分析过后再查看:
select t.table_name,
t.num_rows,
t.chain_cnt,
t.avg_row_len,
round((t.chain_cnt / t.num_rows) * 100, 2) as "chained rows %"
from user_tables t
where t.chain_cnt > 0
如果该表的chain_cnt变为0时,表示原先的chain_cnt全部是行迁移,而不是行链接。如果REBUILD数据后chain_cnt变少,但还大于0,则可以证明,这个表即包含行迁移,又包含真正的行链接。事实证明,行迁移是可以通过REBUILD数据和增加PCTFREE%来清除和减少发生频率的。注意,对于ASSM,PCTUSED,FREELIST,FREELIST GROUPS参数会被忽略。
但对于真正的行链接,只能通过将表移植到大的BLOCSIZE的表空间上,下面是我的实险:
创建一个16K的表空间:
CREATE TABLESPACE LARGETBS BLOCKSIZE 16 K
LOGGING
DATAFILE '/data/app/oracle/oradata/ora33/LARGETBS_01.dbf' SIZE 64M AUTOEXTEND ON NEXT 10M
MAXSIZE UNLIMITED
EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT AUTO ;
在创建的时候报了个ORA-的错,原因我们没有设定16的DB_Buffer_cache,我们设定一下:
alter system set db_16k_cache_size=34603008;
将表MOVE到16K的表空间:
alter table SRC_CS move tablespace LARGETBS;
alter table MM_PM move tablespace LARGETBS;
由于进行了迁移,表的索引会失效,所以我们要REBUILD索引:
alter index PK_SRC_CS rebuild;
alter index PK_MM_PM rebuild;
再重新分析:
analyze table SRC_CS compute statistics;
analyze table MM_PM compute statistics;
重新查询:
select t.table_name,
t.num_rows,
t.chain_cnt,
t.avg_row_len,
round((t.chain_cnt / t.num_rows) * 100, 2) as "chained rows %"
from user_tables t
where t.chain_cnt > 0
order by t.table_name
发现,这些表都没有ROW CHAIN了。可见,MOVE到16K的表空间可以清除ROW CHAIN。
一般来讲,一个表如果有多于256个字段,则发生ROW CHAIN的频率比较高。
对于包含long,long raw类型的行,发生ROW CHAIN的概率也比较大。因为long,long raw会尽量先在同一行中存储字段值。而对于CLOB,BLOB等对象,一般来讲,会单独用另外的表空间来存放(oracle推荐这么做),发生ROW CHAIN的概率小一些。但是,有文档指出,当你没有为CLOB指定单独的表空间时,如果CLOB的实际值小4000 BYTE,ORACLE还是会将CLOB字段存放到同一个BLOCK,只有当CLOB的值大于4000 BYTE,ORACLE才会将该字段单独存放。如果真是这样的话,CLOB也会引起ROW CHAIN(如果CLOB和其它字段是同一表空间的话)。
附件:
create or replace procedure p_analyze_schema is
cursor getdata is
select table_name
from user_tables
order by table_name;
v_sqlstr varchar2(4000);
begin
for rec in getdata loop
v_sqlstr:= 'analyze table '||rec.table_name||' compute statistics';
execute immediate(v_sqlstr);
end loop;
end p_analyze_schema;
请慎重使用下面这个存储过程:
create or replace procedure p_calculate_rowlen is
cursor getdata is
select table_name
from user_tables
where table_name not in
('mm_pm', 'mm_pm_history', 'src_cs', 'src_cs_history')
order by table_name;
cursor getdata2(p_tabname varchar2) is
select t.table_name, t.column_name
from user_tab_columns t
where t.table_name = p_tabname
and t.data_type not in ('clob', 'blob')
order by t.column_id;
v_sqlstr clob;
begin
dbms_output.enable(1000000);
for rec in getdata loop
v_sqlstr := ' ';
for rec2 in getdata2(rec.table_name) loop
v_sqlstr := v_sqlstr || 'nvl(vsize(' || rec2.column_name || '),0)+';
end loop;
v_sqlstr := substr(v_sqlstr, 1, (length(v_sqlstr) - 1));
v_sqlstr := 'insert into t_tab_row_len select ''' || rec.table_name ||
''',rl from ' || ' (select (' || v_sqlstr ||
') as rl from ' || rec.table_name || ') where rl>=1000';
begin
execute immediate (to_char(v_sqlstr));
commit;
exception
when others then
dbms_output.put_line(rec.table_name);
return;
end;
end loop;
end p_calculate_rowlen;
create table t_tab_row_len
(
table_name varchar2(60),
row_len number(20)
)
行链接:是指一个BLOCK不能容纳一行(行的长度太大),而必须将此行存放于几个BLOCK.行链接一般是在Insert时产生的.一个BLOCK能否用于insert是由PCTUSED控制.
pctused(percent used)与pctfree(percent free)是Oracle的两个与性能相关的块级存储参数。虽然我很少修改它们,但是相应的概念还是比较重要的,所以强化一下。
概念:
pctused:一个块的使用水位的百分比,这个水位将使该块返回到可用列表中去等待更多的插入操作。
pctfree:用来为一个块保留的空间百分比,以防止在今后的更新操作中增加一列或多列值的长度。
freelist:可用列表是表中的一组可插入数据的可用块。
行连接:指一行存储在多个块中的情况,这是因为该行的长度超过了一个块的可用空间大小,即行链接是跨越多块的行。
行迁移:指一个数据行不适合放入当前块而被重新定位到另一个块(那里有充足的空间)中,但在原始块中保留一个指针的情形。原始块中的指针是必需的,因为索引的ROWID项仍然指向原始位置。
计算公式:
PCTFREE=(Average Row Size-Initial Row Size)*100/Average Row Size
PCTUSED=(100-PCTFREE) -Average Row Size * 100/Availabe Data Space
Oracle的其中一个优点时它可以管理每个表空间中的自由空间。Oracle负责处理表和索引的空间管理,这样就可以让我们无需懂得Oracle的表和索引的内部运作。不过,对于有经验的Oracle调优专家来说,他需要懂得Oracle是如何管理表的extent和空闲的数据块。对于调整拥有高的 insert或者update的系统来说,这是非常重要的。
要精通对象的调整,你需要懂得freelists和freelist组的行为,它们和pctfree及pctused参数的值有关。这些知识对于企业资源计划(ERP)的应用是特别重要的,因为在这些应用中,不正确的表设置通常是DML语句执行慢的原因。
对于初学者来说,最常见的错误是认为默认的Oracle参数对于所有的对象都是最佳的。除非磁盘的消耗不是一个问题,否则在设置表的pctfree和 pctused参数时,就必须考虑平均的行长和数据库的块大小,这样空的块才会被有效地放到freelists中。当这些设置不正确时,那些得到的 freelists也是"dead"块,因为它们没有足够的空间来存储一行,这样将会导致明显的处理延迟。
Freelists对于有效地重新使用Oracle表空间中的空间是很重要的,它和pctfree及pctused这两个存储参数的设置直接相关。如果将 pctused设置为一个高的值,这时数据库就会尽快地重新使用块。不过,高性能和有效地重新使用表的块是对立的。在调整Oracle的表格和索引时,需要认真考虑究竟需要高性能还是有效的空间重用,并且据此来设置表的参数。以下我们来看一下这些freelists是如何影响Oracle的性能的。
当有一个请求需要插入一行到表格中时,Oracle就会到freelist中寻找一个有足够的空间来容纳一行的块。你也许知道,freelist串是放在表格或者索引的第一个块中,这个块也被称为段头(segment header)。pctfree和pctused 参数的唯一目的就是为了控制块如何在freelists中进出。虽然freelist link和 unlink是简单的Oracle功能,不过设置freelist link (pctused) 和unlink (pctfree) 对Oracle的性能确实有影响。
由DBA的基本知识知道,pctfree参数是控制freelist un-links的(即将块由freelists中移除)。设置pctfree=10 意味着每个块都保留10%的空间用作行扩展。pctused参数是控制freelist re-links的。设置pctused=40意味着只有在块的使用低于40%时才会回到表格的freelists中。
许多新手对于一个块重新回到freelists后的处理都有些误解。其实,一旦由于一个删除的操作而令块被重新加入到freelist中,它将会一直保留在freelist中即使空间的使用超过了60%,只有在到达pctfree时才会将数据块由freelist中移走。
表格和索引存储参数设置的要求总结
以下的一些规则是用来设置freelists, freelist groups, pctfree和pctused存储参数的。你也知道,pctused和pctfree的值是可以很容易地通过alter table命令修改的,一个好的DBA应该知道如何设置这些参数的最佳值。
有效地使用空间和高性能之间是有矛盾的,而表格的存储参数就是控制这个方面的矛盾:
. 对于需要有效地重新使用空间,可以设置一个高的pctused值,不过副作用是需要额外的I/O。一个高的pctused值意味着相对满的块都会放到 freelist中。因此,这些块在再次满之前只可以接受几行记录,从而导致更多的I/O。
. 追求高性能的话,可以将pctused设置为一个低的值,这意味着Oracle不会将数据块放到freelists中直到它几乎是空的。那么块将可以在满之前接收更多的行,因此可以减少插入操作的I/O。要记住Oracle扩展新块的性能要比重新使用现有的块高。对于Oracle来说,扩展一个表比管理 freelists消耗更少的资源。
让我们来回顾一下设置对象存储参数的一些常见规则:
.经常将pctused设置为可以接收一条新行。对于不能接受一行的free blocks对于我们来说是没有用的。如果这样做,将会令Oracle的性能变慢,因为Oracle将在扩展表来得到一个空的块之前,企图读取5 个"dead"的free block。
.表格中chained rows的出现意味着pctfree太低或者是db_block_size太少。在很多情况下,RAW和LONG RAW列都很巨大,以至超过了Oracle的最大块的大小,这时chained rows是不可以避免的。
.如果一个表有同时插入的SQL语句,那么它需要有同时删除的语句。运行单一个一个清除的工作将会把全部的空闲块放到一个freelist中,而没有其它包含有任何空闲块的freelists出现。
.freelist参数应该设置为表格同时更新的最大值。例如,如果在任何时候,某个表最多有20个用户执行插入的操作,那么该表的参数应该设置为 freelists=20。
应记住的是freelist groups参数的值只是对于Oracle Parallel Server和Real Application Clusters才是有用的。对于这类Oracle,freelist groups应该设置为访问该表格的Oracle Parallel Server实例的数目。
一、行迁移/行链接的介绍
Row Migration (行迁移) & Row Chaining (行链接)就是其中我们可以尽量避免的引起Oracle数据库性能低下的潜在问题。通过合理的诊断行迁移/行链接,我们可以较大幅度上提高Oracle数据库的性能。
什么是行迁移/行链接
操作系统的最小读写操作单元是操作系统的block,所以当创建一个Oracle数据库的时候我们应该讲数据库的block size设置成为操作系统的block size的整数倍,Oracle block是Oracle数据库中读写操作的最小单元,Oracle9i之前的Oracle数据库版本中Oracle block一旦在创建数据库的时候被设定后就没法再更改。为了在创建数据库之前确定一个合理的Oracle block的大小,我们需要考虑一些因素,例如数据库本身的大小以及并发事务的数量等。使用一个合适的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来获得这行的信息。
二、行迁移/行链接的检测
行链接主要是由于数据库的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';
可以使用如下的脚本来直接查找存在有行链接和行迁移的表,自动完成所有的分析和统计。
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;
三、行迁移和行链接的清除
由 于对于行链接来说只能增大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;
6.查看在CUSTOMER表上存在的限制
select CONSTRAINT_NAME,CONSTRAINT_TYPE,TABLE_NAME from USER_CONSTRAINTS where R_CONSTRAINT_NAME='PK_CUSTOMER1';
对于这种传统的清除RM的方法,优点是执行起来过程比较简单,容易实现。但是这种算法的缺陷是没有考虑到表关联的情况,在大多数数据库中很多表都是和别的表之间有表关联的,有外键的限制,这样就造成在步骤3中根本无法delete掉存在有行迁移的记录行,所以这种方法能够适用的表的范围是有限的,只能适用于表上无任何外键关联的表。由于这种方法在插入和删除数据的时候都没有disable掉索引,这样导致主要消耗时间是在删除和插入时维持索引树的均衡上了,这个对于如果记录数不多的情况时间上还比较短,但是如果对于记录数很多的表这个所消耗的时间就不是能够接受的。显然,这种方法在处理大数据量的表的时候显然是不可取的。
方法二:改进了的传统清除行迁移的方法
1. 执行$ORACLE_HOME/rdbms/admin目录下的utlchain.sql脚本创建chained_rows表。
@$ORACLE_HOME/rdbms/admin/utlchain.sql
2. 禁用所有其它表上关联到此表上的所有限制。
select index_name,index_type,table_name from user_indexes where table_name='MTL_SYSTEM_ITEMS_B';
select CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME from USER_CONSTRAINTS where table_name = 'MTL_SYSTEM_ITEMS_B';
alter table table_name disable constraint constraint_name;
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;
6. 启用所有其它表上关联到此表上的所有限制。
alter table table_name enable constraint constraint_name;
这种算法是对传统算法的一种改进,对于使用这种算法来清除行迁移,考虑到了表之间的关联,还可以灵活的利用的TOAD工具生成的表关联信息,是一种比较适合于清除行迁移的一种方法。但是因为使用这种方法后来需要重建索引,如果记录数很大,比如说上千万条以上的记录的表,就不是很合适,因为这个重建索引的时间会很长,是线性时间复杂度的,而重建索引是会导致索引所在的表被锁定的,从而导致插入不了新的记录,重建索引的时间太长导致记录长时间插入不了是会严重影响应用的,甚至导致数据的丢失,因此这个是使用这个方法前必须要考虑到的一个重要因素;对于8i以上的版本可以使用online的方法来重建索引,这样不会导致锁表,但是会有额外更多的开销,时间会很长。再者,因为这种方法在插入记录和删除记录都是带着索引的,如果表上的行迁移比较多,这样耗时间会比较长,而且占用资源也会比较大,因此只适用于表上行迁移存在的比较少的表。总的来说,这种方法对于表记录太多或者是表上的行迁移太多的情况都不是很适用,比较适合表记录少和表上行迁移都不太多的情况。
方法三:使用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 constraint_name;
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导出存在有行迁移的表。
select count(*) from table_name;
truncate table chained_rows;
analyze table table_name LIST CHAINED ROWS INTO chained_rows;
select count(*) from chained_rows;
$ exp allan/allan file=test.dmp tables=test
$ sqlplus allan/allan
2. 然后TRUNCATE原来的表。
truncate table test;
3. IMP开始导出的表。
imp allan/allan file=test.dmp full=y ignore=y buffer=5000000
select count(*) from table_name;
4. 重建表上所有的索引。(可选)
select index_name from user_indexes where table_name='TEST';
alter index OBJ_INDEX rebuild online;
truncate table chained_rows;
analyze table test LIST CHAINED ROWS INTO chained_rows;
select count(*) from chained_rows;
使用这种方法可以不用重建索引,省去了这部分时间,但是完成之后索引的使用效率不会很高,最好是在以后逐步的在线重建索引,这样是可以不需要中断业务的。但是需要考虑的是IMP的时候会比较慢,而且会占用比较大的IO,应该选择在应用不是很繁忙的时候做这项工作,否则会对应用的正常运行产生较大的影响。对于这种方法还存在有一个比较大的弊端,就是在EXP表的时候要保证该表是没有数据的更新或者是只读状态的,不能对表有插入或者更新操作,否则会导致数据的丢失。
方法五:使用MOVE命令来清除行迁移的方法
1. 查看要清除行迁移的表所在的表空间。
ANALYZE TABLE table_name LIST CHAINED ROWS INTO chained_rows;
SELECT count(*) from chained_rows;
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';
select count(*) from table_name;
3. Move要清除RM的表到指定的表空间中去。
alter table table_name move tablespace tablespace_name;
4. 重建表上的所有索引。
alter index index_name rebuild;-----
truncate table chained_rows;
ANALYZE TABLE table_name LIST CHAINED ROWS INTO chained_rows;
SELECT count(*) from chained_rows;
这种方法适用于8i及其以上的数据库版本,主要是利用数据库的一个MOVE命令来实现行迁移的清除的,MOVE命令的实质其实就是INSERT … SELECT的一个过程,在MOVE表的过程中是需要两倍的原来的表大小的,因为中间过程是要保留原来的旧表的,新表创建完成后旧表就被删除并释放空间了。MOVE的时候要注意后面一定要加上表空间参数,所以必须要先知道表所在的表空间;因为MOVE表之后需要重建索引,所以之前要确定表上的所有的索引。
这种方法对于表记录数很大或者表上索引太多的情况不太适用,因为本身的MOVE就会很慢, 而且MOVE表的时候会要锁定表,时间长了会导致对表的其他操作出现问题,导致数据插入不了丢失数据;MOVE表后还要重建索引,索引太多了的话重建的时间也会太长;再者,这个方法也比较消耗资源,因此强烈建议在业务不繁忙的时候再执行。
方法六:对于一些行迁移数量巨大而且表记录数巨大的表的行迁移的清除方法
1. 使用TOAD工具或者别的方法获取存在有大量行迁移并且表记录很大的表的重建表的SQL,然后保存为脚本。
2. 使用RENAME命令将原始表重命名为一个备份表,然后删除别的表对原始表上的限制、以及原始表上的外键和索引。
3. 利用1中生成的脚本重建原始表,以及表上的限制,外键,索引等对象。
4. 然后按表模式导出2中备份的表,然后导入到另外的一个临时中转的数据库库中,因为表的名字已经改变,所以导入后需要RENAME表为原来的名字,然后重新导出,最后再导入到原来的数据库中。
这种方法主要是用来针对一些数据量比较大,并且表上的行迁移也比较多的表的行迁移清除。对于这些大表的行迁移的清除,正常来说都需要停应用一段较长时间才能够清除掉,让人感觉比较的头疼,对于7*24小时的应用来说,down机的时间越长损失则越大,当然是要尽量的减短down机的时间。但是因为表本身比较大,不管怎样做什么操作都是会比较耗费时间和资源的,但是如果应用在某段时间内主要是以插入数据为主,更新数据和删除数据都很少的,因此可以考虑可以采用这么一种方法:先重命名表,然后重新建立一个和原来一样的表,用来保证之后的应用的数据是可以正常插入的,从而使应用不用停很久,因为重建一个没有任何数据的表结构的过程是很短暂的,大概需要几秒钟的时间,而重建好表了后就能保证应用能够正常的写入数据,从而使应用几乎不用停顿,然后把开始重命名的原始表按表模式导出,因为表的名字已经被改变,因此需要一个临时库来导入这些数据,然后重命名回原来的名字,然后按原来的表名导出后再重新导入原始数据库,这样操作起来虽然会比较麻烦,但是却是一种很有效很实际的方法,速度也很快,导出后导入,因为本身表结构已经建立好了,不需要其他任何的多的操作,而且最关键的是这种方法所需要的down机时间是最短的。
Eg:
ALTER TABLE USER.PAY RENAME TO PAY_X ;
然后导出PAY_X表;
$ exp USER/USER file=PAY_X.dmp tables=PAY_X;
ALTER TABLE USER.BATCHPAYMENTDETAIL DROP CONSTRAINT FK_BATCHPAYMENTDETAIL_OPAYID ;
ALTER TABLE USER.DEPOSITCLASSIFY DROP CONSTRAINT FK_DEPOSITCLASSIFY2 ;
ALTER TABLE USER.DEPOSITCREDITLOG DROP CONSTRAINT FK_DEPOSITCREDITLOG2 ;
ALTER TABLE USER.DEPOSIT DROP CONSTRAINT SYS_C003423 ;
ALTER TABLE USER.PAY_X DROP CONSTRAINT SYS_C003549 ;
DROP INDEX USER.I_PAY_STAFFID ;
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;
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;
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;
ALTER TABLE USER.PAY ADD ( FOREIGN KEY (STAFFID)REFERENCES USER.STAFF (STAFFID));
ALTER TABLE USER.DEPOSITCLASSIFY ADD CONSTRAINT FK_DEPOSITCLASSIFY2 FOREIGN KEY (PAYID) REFERENCES USER.PAY (PAYID) ;
ALTER TABLE USER.DEPOSITCREDITLOG ADD CONSTRAINT FK_DEPOSITCREDITLOG2 FOREIGN KEY (PAYID) REFERENCES USER.PAY (PAYID) ;
ALTER FUNCTION "USER"."GENERATEPAYNO" COMPILE ;
ALTER PROCEDURE "USER"."ENGENDERPRVPAY" COMPILE ;
ALTER PROCEDURE "USER"."ISAP_ENGENDERPRVPAY" COMPILE ;
ALTER PROCEDURE "USER"."SPADDCREDITDEPOSIT" COMPILE ;
ALTER PROCEDURE "USER"."SPADDDEPOSITWITHOUTCARD" COMPILE ;
ALTER PROCEDURE "USER"."SPADJUSTLWDEPOSIT" COMPILE ;
imp USER/USER file= PAY_x.dmp tables=PAY ignore=y
rename PAY_X to PAY;
exp USER/USER file=PAY.dmp tables=PAY
转载:http://blog.csdn.net/rainnyzhong/archive/2010/03/12/5374588.aspx
美国PVH DB存在行链接.通过我的检查,发现,我们的DB同样存在行迁移和行链接.ORACLE的dbms_stats.gather_schema_stats只会收集优化器统计信息,不会检测表的记录是否存在行迁移和行链接.(我原先被这个包给骗了,所以执行完dbms_stats.gather_schema_stats后一查数据字典:USER_TABLES,其中的字段chain_cnt值全为0).要检测表的记录是否存在row chain,需要用到ORACLE早先一点的命令:ANALYZE TABLE.所以我写了一个存储过程(P_analyze_schema)以检测DB:PVH91USRD中所有表存在行链接的情况.
值得注意的是:数据字典 USER_TABLES.CHAIN_CNT的值是包含了行迁移和行链接的数目.请看ORACLE的解释:
CHAIN_CNT* Number of rows in the table that are chained from one data block to another, or which have migrated to a new block, requiring a link to preserve the old ROWID
也就是说这个字段的值是行迁移和行链接的总数量,至于要知道具体行迁移有多少,行链接又是多少,ANALYZE TABLE这个命令没办法得到,只有通过DUMP BLOCK来区分(方法复杂,故没有去做).但首先我们要知道行迁移和行链接是两个不同的概念:
(1)行迁移: ORACLE一个BLOCK的DEFAULT SIZE是8K,事实上,一个BLOCK不可以存储8K的数据.一个BLOCK可以存储多少数据,由PCTFREE,PCTUSED参数控制(对于以前的手工管理的表空间而言).
PCTFREE:是指BLOCK保留空闲空间的百分比,用于UPDATE。对于已经插入到BLOCK的行而言,后面的UPDATE操作有可能使行的长度增加,PCTFREE就是用于容纳增加的那部分长度而保留的空闲空间。如果UPDATE时PCTFREE再也不能够容纳行增加的长度,则ORACLE会将整个行迁移到一个新的BLOCK,行的ROWID保留(不是太明白为什么ORACLE不改变ROWID),原来的BLOCK有一个指针指向ROW存放的新BLOCK。这就是行迁移。可见,行迁移是由于UPDATE操作所导致。从字面上理解,所谓迁移,肯定先有存在这一行,才能叫着迁移.
PCTUSED:是指BLOCK用于INSERT的百分比。对于INSERT操作,BLOCK可用于容纳新行的最大空间为Blocksize-pctfree-overhead.当BLOCK数据存储已高于PCTUSED,ORACLE会将该块从自由链表中移除,直到该块已使用空间降到PCTUSED以下,才会再次将此块重新加入到Freelist(这是ORACLE以前手工管理的表空间管理空闲块的原理,现在ORACLE推荐使用ASSM).
行迁移和行链接的检测:
除了我写的存储过程可以检测以外,一个简单的检测方法是:
select b.NAME,a.VALUE from v$mystat a,v$statname b
where a.STATISTIC#=b.STATISTIC#
and b.NAME like 'table fetch continued row'
当有返回值时,可以知道表的数据存在行迁移和行链接。
行迁移和行链接的清除:
能过REBUILD数据来清除行迁移:
create table MM_PM_temp as select * from MM_PM;
truncate table MM_PM;
insert into MM_PM select * from MM_PM_temp
再重新分析表:
analyze table MM_PM compute statistics;
分析过后再查看:
select t.table_name,
t.num_rows,
t.chain_cnt,
t.avg_row_len,
round((t.chain_cnt / t.num_rows) * 100, 2) as "chained rows %"
from user_tables t
where t.chain_cnt > 0
如果该表的chain_cnt变为0时,表示原先的chain_cnt全部是行迁移,而不是行链接。如果REBUILD数据后chain_cnt变少,但还大于0,则可以证明,这个表即包含行迁移,又包含真正的行链接。事实证明,行迁移是可以通过REBUILD数据和增加PCTFREE%来清除和减少发生频率的。注意,对于ASSM,PCTUSED,FREELIST,FREELIST GROUPS参数会被忽略。
但对于真正的行链接,只能通过将表移植到大的BLOCSIZE的表空间上,下面是我的实险:
创建一个16K的表空间:
CREATE TABLESPACE LARGETBS BLOCKSIZE 16 K
LOGGING
DATAFILE '/data/app/oracle/oradata/ora33/LARGETBS_01.dbf' SIZE 64M AUTOEXTEND ON NEXT 10M
MAXSIZE UNLIMITED
EXTENT MANAGEMENT LOCAL
SEGMENT SPACE MANAGEMENT AUTO ;
在创建的时候报了个ORA-的错,原因我们没有设定16的DB_Buffer_cache,我们设定一下:
alter system set db_16k_cache_size=34603008;
将表MOVE到16K的表空间:
alter table SRC_CS move tablespace LARGETBS;
alter table MM_PM move tablespace LARGETBS;
由于进行了迁移,表的索引会失效,所以我们要REBUILD索引:
alter index PK_SRC_CS rebuild;
alter index PK_MM_PM rebuild;
再重新分析:
analyze table SRC_CS compute statistics;
analyze table MM_PM compute statistics;
重新查询:
select t.table_name,
t.num_rows,
t.chain_cnt,
t.avg_row_len,
round((t.chain_cnt / t.num_rows) * 100, 2) as "chained rows %"
from user_tables t
where t.chain_cnt > 0
order by t.table_name
发现,这些表都没有ROW CHAIN了。可见,MOVE到16K的表空间可以清除ROW CHAIN。
一般来讲,一个表如果有多于256个字段,则发生ROW CHAIN的频率比较高。
对于包含long,long raw类型的行,发生ROW CHAIN的概率也比较大。因为long,long raw会尽量先在同一行中存储字段值。而对于CLOB,BLOB等对象,一般来讲,会单独用另外的表空间来存放(oracle推荐这么做),发生ROW CHAIN的概率小一些。但是,有文档指出,当你没有为CLOB指定单独的表空间时,如果CLOB的实际值小4000 BYTE,ORACLE还是会将CLOB字段存放到同一个BLOCK,只有当CLOB的值大于4000 BYTE,ORACLE才会将该字段单独存放。如果真是这样的话,CLOB也会引起ROW CHAIN(如果CLOB和其它字段是同一表空间的话)。
附件:
create or replace procedure p_analyze_schema is
cursor getdata is
select table_name
from user_tables
order by table_name;
v_sqlstr varchar2(4000);
begin
for rec in getdata loop
v_sqlstr:= 'analyze table '||rec.table_name||' compute statistics';
execute immediate(v_sqlstr);
end loop;
end p_analyze_schema;
请慎重使用下面这个存储过程:
create or replace procedure p_calculate_rowlen is
cursor getdata is
select table_name
from user_tables
where table_name not in
('mm_pm', 'mm_pm_history', 'src_cs', 'src_cs_history')
order by table_name;
cursor getdata2(p_tabname varchar2) is
select t.table_name, t.column_name
from user_tab_columns t
where t.table_name = p_tabname
and t.data_type not in ('clob', 'blob')
order by t.column_id;
v_sqlstr clob;
begin
dbms_output.enable(1000000);
for rec in getdata loop
v_sqlstr := ' ';
for rec2 in getdata2(rec.table_name) loop
v_sqlstr := v_sqlstr || 'nvl(vsize(' || rec2.column_name || '),0)+';
end loop;
v_sqlstr := substr(v_sqlstr, 1, (length(v_sqlstr) - 1));
v_sqlstr := 'insert into t_tab_row_len select ''' || rec.table_name ||
''',rl from ' || ' (select (' || v_sqlstr ||
') as rl from ' || rec.table_name || ') where rl>=1000';
begin
execute immediate (to_char(v_sqlstr));
commit;
exception
when others then
dbms_output.put_line(rec.table_name);
return;
end;
end loop;
end p_calculate_rowlen;
create table t_tab_row_len
(
table_name varchar2(60),
row_len number(20)
)
行链接:是指一个BLOCK不能容纳一行(行的长度太大),而必须将此行存放于几个BLOCK.行链接一般是在Insert时产生的.一个BLOCK能否用于insert是由PCTUSED控制.