zoukankan      html  css  js  c++  java
  • Oracle中HWM与数据库性能的探讨

    Oracle中HWM与数据库性能的探讨

    一、什么是高水位

    HWM(high water mark),高水标记,这个概念在segment的存储内容中是比较重要的.简单来说,HWM就是一个segment中已使用和未使用的block的分界线.

    在oracle的concept中对于HWM的说明是这样的:在一个segment中,HWM是使用和未使用空间的分界线。HWM在插入数据时,当现有空间不足而进行空间的扩展时会向上移,但删除数据时不会往下移。这就好比是水库的水位,当涨水时,水位往上移,当水退出后,最高水位的痕迹还是清淅可见.考虑让我们看一个段,如一张表,其中填满了块,如图 1 所示。在正常操作过程中,删除了一些行,如图 2 所示。现有就有了许多浪费的空间:(I) 在表的上一个末端和现有的块之间,以及 (II) 在块内部,其中还有一些没有删除的行。

    图1:分配给该表的块。用灰色正方形表示行 

    ORACLE 不会释放空间以供其他对象使用,有一条简单的理由:由于空间是为新插入的行保留的,并且要适应现有行的增长。被占用的最高空间称为最高使用标记 (HWM),如图 2 所示。


    图2:行后面的块已经删除了;HWM 仍保持不变

    HWM本身的信息是储存在段头.在段空间是手工管理方式时,ORACLE是通过FREELIST(一个单向链表)来管理段内的空间分配.在段空间是自动管理方式时(ASSM),ORACLE是通过BITMAP来管理段内的空间分配.

    本文的实验环境windows2003,oracle 10.2.0.1.0,bolcksize 8K。

    二、HWM对性能的影响

    这里我们要先引入一个procedure(转自tom的《oracle高级专家编程》):

    create or replace procedure show_space

    ( p_segname in varchar2,

     p_owner  in varchar2 default user,

     p_type   in varchar2 default 'TABLE',

     p_partition in varchar2 default NULL )

    as

       l_total_blocks             number;

       l_total_bytes              number;

       l_unused_blocks            number;

       l_unused_bytes             number;

       l_LastUsedExtFileId        number;

       l_LastUsedExtBlockId       number;

       l_last_used_block          number;

       procedure p( p_label in varchar2, p_num in number )

       is

       begin

           dbms_output.put_line( rpad(p_label,40,'.') || p_num );

       end;

    begin

       dbms_space.unused_space

       ( segment_owner    => p_owner,

         segment_name     => p_segname,

         segment_type     => p_type,

         partition_name   => p_partition,

         total_blocks     => l_total_blocks,

         total_bytes      => l_total_bytes,

         unused_blocks    => l_unused_blocks,

         unused_bytes     => l_unused_bytes,

         last_used_extent_file_id => l_LastUsedExtFileId,

         last_used_extent_block_id => l_LastUsedExtBlockId,

         last_used_block => l_last_used_block );

       p( 'Total Blocks', l_total_blocks );

       p( 'Total Bytes', l_total_bytes );

       p( 'Unused Blocks', l_unused_blocks );

       p( 'Unused Bytes', l_unused_bytes );

       p( 'Last Used Ext FileId', l_LastUsedExtFileId );

       p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );

       p( 'Last Used Block', l_last_used_block );

    end;

    通过这个procedure显示的结果,我们可以得到一个segment的HWM的位置。在sqlplus中,我们要看到这个procedure显示的结果,需要设置: set serveroutput on。

    这里,HWM = total_blocks - Unused Blocks +1。

    我们对一个table进行DML操作,主要是insert,update,delete这三种。当一个table进行了insert数据时,table的HWM会怎样?

    我们先做如下实验:

    创建表T1,查看高水位情况,插入5000条记录,查看高水位情况,再插入5000条,再查看高水位情况。

    SQL> create table t1(id int,name varchar2(30)); 

    表以创建

    SQL> exec show_space('T1');

    Total Blocks............................8

    Total Bytes.............................65536

    Unused Blocks...........................5

    Unused Bytes............................40960

    Last Used Ext FileId....................4

    Last Used Ext BlockId...................1041

    Last Used Block.........................3

    PL/SQL过程已成功完成

    SQL>begin

      2  for i in 1..5000 loop

      3  insert into t1 values (i,i||'__aa');

      4  end loop;

      5  commit;

      6  end;

      7  /

    PL/SQL过程已成功完成

    SQL> exec show_space('T1');

    Total Blocks............................16

    Total Bytes.............................131072

    Unused Blocks...........................0

    Unused Bytes............................0

    Last Used Ext FileId....................4

    Last Used Ext BlockId...................1049

    Last Used Block.........................8

    PL/SQL 过程已成功完成。

    SQL> begin

      2  for i in 5001..1000 loop

      3  insert into t1 values(i,i||'__aaa');

      4  end loop;

      5  commit;

      6  end;

      7  /

    PL/SQL 过程已成功完成。

    SQL> exec show_space('T1');

    Total Blocks............................32

    Total Bytes.............................262144

    Unused Blocks...........................0

    Unused Bytes............................0

    Last Used Ext FileId....................4

    Last Used Ext BlockId...................1073

    Last Used Block.........................8

    T1表创建后         HWM=8-5+1=4;

    插入5000条记录后   HWM=16-0+1=17;

    再插入5000条记录后 HWM=32-0+1=33;

    我们可以看到表在进行数据插入时HWM会不停地提升。现在我们来这样一种情况:如果在这期间我们对这个table进行了大量的delete操作,这是table的HWM会不会随着数据量的减少而下降呢?我们将通过一个实验来说明这个问题:

    删除5000条记录:

    SQL> DELETE FROM T1 WHERE ROWNUM<=5000; 

    已删除5000行

    SQL>cimmit;

    提交完成

    SQL> exec show_space('T1');

    Total Blocks............................32

    Total Bytes.............................262144

    Unused Blocks...........................0

    Unused Bytes............................0

    Last Used Ext FileId....................4

    Last Used Ext BlockId...................1073

    Last Used Block.........................8

    现在我们再来观察HWM的结果,可以看到:这里HWM=32 - 0 + 1 = 33。

    HWM的位置并没有发生变化。这说明对TABLE T1删除了5000行数据后,并不会改变HWM的位置。

    那么,HWM过高会对数据库的性能有什么样的影响呢?

    这里我们以全表扫描为例,来讨论HWM过高的不良影响。

    同样,我们也通过一个实验来看full table scan在delete前后访问的block数量的情况:

    SQL> set autotrace traceonly

    SQL> select count(1) FROM T1;

    执行计划

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

    Plan hash value: 3724264953

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

    | Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |

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

    |   0 | SELECT STATEMENT   |      |     1 |     9   (0)| 00:00:01 |

    |   1 |  SORT AGGREGATE    |      |     1 |            |          |

    |   2 |   TABLE ACCESS FULL| T1   | 10000 |     9   (0)| 00:00:01 |

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

    Note

    -----

       - dynamic sampling used for this statement

    统计信息

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

              1  recursive calls

              0  db block gets

             31  consistent gets

              0  physical reads

              0  redo size

            408  bytes sent via SQL*Net to client

            385  bytes received via SQL*Net from client

              2  SQL*Net roundtrips to/from client

              0  sorts (memory)

              0  sorts (disk)

              1  rows processed

    我们可以看到表在删除了5000条记录后进行全表扫描时,仍然要扫描31个数据块。

    我们将表的数据清空,看访问的block是否会减少。

    SQL> delete from t1;

    已删除5000行

    SQL> commit;

    提交完成

    在这里,我们把oracle先shutdown,然后在startup,以便清空cache中的数据。

    SQL> select count(1) FROM T1;

    执行计划

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

    Plan hash value: 3724264953

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

    | Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |

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

    |   0 | SELECT STATEMENT   |      |     1 |     9   (0)| 00:00:01 |

    |   1 |  SORT AGGREGATE    |      |     1 |            |          |

    |   2 |   TABLE ACCESS FULL| T1   | 10000 |     9   (0)| 00:00:01 |

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

    Note

    -----

       - dynamic sampling used for this statement

    统计信息

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

              0  recursive calls

              0  db block gets

              0  consistent gets

              0  physical reads

              0  redo size

              0  bytes sent via SQL*Net to client

              0  bytes received via SQL*Net from client

              0  SQL*Net roundtrips to/from client

              0  sorts (memory)

              0  sorts (disk)

              1  rows processed

    怎么会是0呢,再少也不应该是0啊。通过不断的百度,终于发现原因是一个bug。如果重启过系统且没有退出SQLPLUS,再次登陆后,启用AUTOTRACE后,除了处理行数外的其他统计信息均为0。

    我们退出sqlplus后,重新登录后

    SQL> set autotrace traceonly

    SQL> select count(1) FROM T1;

    执行计划

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

    Plan hash value: 3724264953

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

    | Id  | Operation          | Name | Rows  | Cost (%CPU)| Time     |

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

    |   0 | SELECT STATEMENT   |      |     1 |     9   (0)| 00:00:01 |

    |   1 |  SORT AGGREGATE    |      |     1 |            |          |

    |   2 |   TABLE ACCESS FULL| T1   | 10000 |     9   (0)| 00:00:01 |

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

    Note

    -----

       - dynamic sampling used for this statement

    统计信息

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

              1  recursive calls

              0  db block gets

             31  consistent gets

              0  physical reads

              0  redo size

            408  bytes sent via SQL*Net to client

            385  bytes received via SQL*Net from client

              2  SQL*Net roundtrips to/from client

              0  sorts (memory)

              0  sorts (disk)

              1  rows processed

    我们可以看到oracle访问的block并没有减少。这说明进行table full scan时,实际上是对HWM下所有的block进行访问。我们知道,访问的block数量越多,代表需要消耗的资源越多。那么,当一个table在进行了大量的delete操作后,或者说,当一个table在HWM之下的block上的数据不饱和时,我们应该考虑采用一些方法来降低该表的HWM,以减小table full scan时需要访问的block数量。

    三、如何降低HWM

    oracle8i以前的版本,如果我们需要降低segment的HWM,只能采用两种方法:EXP/IMP和CTAS。

    从8i开始我们可以使用move来降低高水位,实现原理是将一个table segment从一个tablespace移动到另一个tablespace。

    从9i开始我们可以使用DBMS_REDEFINITION,我们可以通过这种方法在线地重组table,来移动table中的数据,降低HWM,修改table的存储参数,分区等等。

    从10g开始,oracle开始提供Shrink的命令,假如我们的表空间中支持自动段空间管理(ASSM),就可以使用这个特性缩小段,即降低HWM。这里需要强调一点,10g的这个新特性,仅对ASSM表空间有效,否则会报 ORA-10635: Invalid segment or tablespace type。

    现在公司使用的主要是10g的数据块,所以我们只对shrink做讨论。

    我们只考虑ASSM的情况,首先创建一个procedure(摘自盖国强的深入解析ORACLE)来得到table的blocks使用情况。

    create or replace procedure show_space_assm(

    p_segname in varchar2,

    p_owner in varchar2 default user,

    p_type in varchar2 default 'TABLE' )   

    as 

    l_fs1_bytes number;

    l_fs2_bytes number;

    l_fs3_bytes number;

    l_fs4_bytes number;

    l_fs1_blocks number;

    l_fs2_blocks number;

    l_fs3_blocks number;

    l_fs4_blocks number;

    l_full_bytes number;

    l_full_blocks number;

    l_unformatted_bytes number;

    l_unformatted_blocks number;  

    procedure p( p_label in varchar2, p_num in number )

    is

    begin

    dbms_output.put_line( rpad(p_label,40,'.') ||p_num );

    end;

    begin

    dbms_space.space_usage(

    segment_owner      => p_owner,

    segment_name       => p_segname,

    segment_type       => p_type,

    fs1_bytes          => l_fs1_bytes,

    fs1_blocks         => l_fs1_blocks,

    fs2_bytes          => l_fs2_bytes,

    fs2_blocks         => l_fs2_blocks,

    fs3_bytes          => l_fs3_bytes,

    fs3_blocks         => l_fs3_blocks,

    fs4_bytes          => l_fs4_bytes,

    fs4_blocks         => l_fs4_blocks,

    full_bytes         => l_full_bytes,

    full_blocks        => l_full_blocks,

    unformatted_blocks => l_unformatted_blocks,

    unformatted_bytes  => l_unformatted_bytes);  

    p('free space 0-25% Blocks:',l_fs1_blocks); 

    p('free space 25-50% Blocks:',l_fs2_blocks);

    p('free space 50-75% Blocks:',l_fs3_blocks);

    p('free space 75-100% Blocks:',l_fs4_blocks);

    p('Full Blocks:',l_full_blocks);

    p('Unformatted blocks:',l_unformatted_blocks);

    end;

    /

    简单地介绍一下rowid的相关知识:

    ROWID在磁盘上需要10个字节的存储空间并使用18个字符来显示它包含下列组件:

    数据对象编号:每个数据对象如表或索引在创建时分配,并且此编号在数据库中是唯一的;

    相关文件编号:此编号对于一个表空间中的每个文件是唯一的;

    块编号:表示包含此行的块在文件中的位置;

    行编号:标识块头中行目录位置的位置;

    在内部数据对象编号需要32位,相关文件编号需要10位,块编号需要22,位行编号需要16位,加起来总共是80位或10个字节,ROWID使用以64进制数的编码方案来显示该方案将六个位置用于数据对象,编号三个位置用于相关文件编号六个位置用于块编号三个位置用于行编号以64进制数的编码方案使用字符A-Z a-z 0-9 +和/共64个字符。

    借助dbms_rowid 包函数我们可以很清楚的知道一条记录的存储位置

    create or replace function get_rowid

    (l_rowid in varchar2)

    return varchar2

    is

    ls_my_rowid     varchar2(200);          

    rowid_type     number;          

    object_number     number;          

    relative_fno     number;          

    block_number     number;          

    row_number     number;  

    begin

     dbms_rowid.rowid_info(l_rowid,rowid_type,object_number,relative_fno, block_number, row_number);          

     ls_my_rowid := 'Object# is      :'||to_char(object_number)||chr(10)||

            'Relative_fno is :'||to_char(relative_fno)||chr(10)||

            'Block number is :'||to_char(block_number)||chr(10)||

            'Row number is   :'||to_char(row_number);

     return ls_my_rowid ;

    end;          

    /

    现在我们重新插入T1表10000条记录,然后保留第一条和最后一条

    SQL> begin

      2  for i in 1..10000 loop

      3  insert into t1 values(i,i||'__aa');

      4  end loop;

      5  commit;

      6  end;

      7  /

    PL/SQL 过程已成功完成。

    SQL> delete from t1 where id between 2 and 9999;

    已删除9998行。

    SQL> commit;

    提交完成。

    SQL> exec show_space_ASSM('T1');

    free space 0-25% Blocks:................0

    free space 25-50% Blocks:...............0

    free space 50-75% Blocks:...............0

    free space 75-100% Blocks:..............28

    Full Blocks:............................0

    Unformatted blocks:.....................0

    PL/SQL 过程已成功完成。

    SQL> select get_rowid(rowid) from t1;

    GET_ROWID(ROWID)

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

    Object# is      :52967

    Relative_fno is :4

    Block number is :1131

    Row number is   :259

    Object# is      :52967

    Relative_fno is :4

    Block number is :1146

    Row number is   :0

    我们可以看到所有的块free space都在75-100%之间。且两条记录分散在不同的块中。

    要使用ASSM上的shrink,首先我们需要使该表支持行移动,可以用这样的命令来完成:

    alter table t1 enable row movement;

    现在,就可以来降低my_objects的HWM,回收空间了,使用命令:

    alter table t1 shrink space;

    我们具体的看一下实验的结果:

    SQL> alter table t1 enable row movement;

    表已更改。

    SQL> alter table t1 shrink space;

    表已更改。

    SQL> exec show_space_ASSM('T1');

    free space 0-25% Blocks:................0

    free space 25-50% Blocks:...............0

    free space 50-75% Blocks:...............0

    free space 75-100% Blocks:..............1

    Full Blocks:............................0

    Unformatted blocks:.....................0

    PL/SQL 过程已成功完成。

    SQL> select get_rowid(rowid) from t1;

    GET_ROWID(ROWID)

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

    Object# is      :52967

    Relative_fno is :4

    Block number is :1124

    Row number is   :403

    Object# is      :52967

    Relative_fno is :4

    Block number is :1124

    Row number is   :404

    当执行了shrink操作后,我们看到两条记录的rowid都发生了变化,且集中到了同一个block中,且都不在原始的block中。 

    我们还可以在shrink table的同时shrink这个table上的index:

    alter table my_objects shrink space cascade;

    同样地,这个操作只有当table上的index也是ASSM时,才能使用。

    segment shrink分为两个阶段:

    1、数据重组(compact):通过一系列insert、delete操作,将数据尽量排列在段的前面。在这个过程中需要在表上加RX锁,即只在需要移动的行上加锁。由于涉及到rowid的改变,需要enable row movement.同时要disable基于rowid的trigger.这一过程对业务影响比较小。

    2、HWM调整:第二阶段是调整HWM位置,释放空闲数据块。此过程需要在表上加X锁,会造成表上的所有DML语句阻塞。在业务特别繁忙的系统上可能造成比较大的影响。

    shrink space语句两个阶段都执行。

    shrink space compact只执行第一个阶段。

    如果系统业务比较繁忙,可以先执行shrink space compact重组数据,然后在业务不忙的时候再执行shrink space降低HWM释放空闲数据块。

    shrink必须开启行迁移功能。

    alter table table_name enable row movement ;

    注意:alter table XXX enable row movement语句会造成引用表XXX的对象(如存储过程、包、视图等)变为无效。

    四、其余移动HWM的操作

    还有几种操作是可以移动HWM的:insert append,truncate。

    当我们使用insert /*+ append */ into向一个table中插入数据时,oracle不会在HWM以下寻找空间,而是直接移动HWM,从EMPTY_BLOCKS中获得要使用的block空间,来满足这一操作的blocks的需要。

    truncate table一般和delete作比较,delete产生rollback,如果删除大数据量的表速度会很慢,同时会占用很多的rollback segments。truncate 是DDL操作,不产生rollback,释放除了MINEXTENTS分配的空间外的所有空间。速度快一些。

  • 相关阅读:
    怎么用JQUERY设置div背景图片?
    为什么导入本地jquery.js老是无效?(已解决)
    问题:AJAX的send参数里,空格以及它后面的数据在传递时消失(已解决)
    问题:win7下配置好服务器就是不能查询数据库。(已解决)
    问题:怎么把mysql的系统时间调整为电脑的时间?(已解决)
    jira安装说明
    HTTP 响应时发生错误。这可能是由于服务终结点绑定未使用 HTTP 协议造成的。这还可能是由于服务器中止了 HTTP 请求上下文(可能由于服务关闭)所致。
    C# 清空控制台屏幕内容
    NPOI导出Excel,添加图片和设置格式,添加条形码
    JS简写
  • 原文地址:https://www.cnblogs.com/hllnj2008/p/4785546.html
Copyright © 2011-2022 走看看