关于Oracle数据库中行迁移/行链接的问题 (1)

 关于Oracle数据库中行迁移/行链接的问题,在网上的这篇文章比较的好,对于深入理解行迁移/行链接以及如何消除行迁移/行链接带来的性能问题,我将它存放到我的BLOG,供自己认真学习。

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

        在实际的工作中我们经常会碰到一些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由三个部分组成,分别是数据块头、自由空间、实际数据三部份组成。

        数据块头:主要包含有数据块地址的一些基本信息和段的类型,以及表和包含有数据的实际行的地址。

        自由空间:是指可以为以后的更新和插入操作分配的空间,大小由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来寻找自由空间。


        具体我们通过下面的一个试验来查看行链接和行迁移是如何产生并在数据文件中体现出来的。


        先查看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

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值