HWM的一点研究

今天一位同事发现11g环境中一张实验数据表筛选变得很慢,即使为空表。通过EM观察,发现指定sql语句等待时间和执行时间都很长(达到秒级)。

 

经过进一步询问,确定该表曾经进行批量数据DML操作,多次重复操作。而且数据表包括分区信息。所以初步认为是高水位线HWM过大引起的问题。

 

在Oracle中,数据表的存储结构是一种类似河道水位的结构。数据表段(segment)就在一系列不连续的区(extents)进行空间的划分,而高水位线就是表示数据表边界的上限标记。

 

以堆表为例,当一行数据为插入时,首先会在高水位线以下随机寻找一块空闲的空间进行存储。如果在HWM以下不能找到合适的位置,就会提升HWM位置,在新的空间上进行存储。但是,当对数据进行删除的时候,HWM时不会主动降低的,就像河道的最高水位一样,永远是标记河水最高位置。

 

这个特性在查找的时候,就会带来一些问题。Oracle在筛选数据,特别是进行全表扫描的时候,要从数据段低一直找到HWM的位置,不管中间经过多少空block。如果数据表数据量很少,但是曾经保存过大量数据,那么有时会带来一些性能问题,如select速度慢。

 

下面通过一个小实验去证明:

1、实验环境获取一下。

SQL> select * from v$version;

 

BANNER

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

Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Prod

PL/SQL Release 10.2.0.1.0 - Production

CORE    10.2.0.1.0    Production

 

TNS for 32-bit Windows: Version 10.2.0.1.0 - Production

NLSRTL Version 10.2.0.1.0  Production

 

2、建立一张大数据表;

 

SQL> create table t as select * from dba_objects where 1=0;

Table created

 

SQL> insert /* +append */ into t select * from dba_objects;

201452 rows inserted

 

SQL> select count(*) from t;

  COUNT(*)

----------

201452

 

SQL> exec dbms_stats.gather_table_stats('SYS','T',cascade => true);

PL/SQL procedure successfully completed

 

3、观察数据段分配情况

调用dba_segment视图,可以查看到数据表t的分配情况。

SQL> col segment_name format a10;

SQL> select segment_name, blocks, extents, INITIAL_EXTENT from dba_segments where wner='SYS' and segment_name='T';

 

SEGMENT_NA     BLOCKS    EXTENTS INITIAL_EXTENT

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

T                2816         37          65536

 

说明:数据表段一共使用了2816个Oracle数据块,分布在37个区extents上。共占用空间2816*8*1024=23068672kb的空间。

 

收集统计信息并且执行

SQL> exec dbms_stats.gather_table_stats('SYS','T',cascade => true);

 

PL/SQL procedure successfully completed

 

SQL> select * from t;

 

已选择201452行。

 

 

执行计划

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

Plan hash value: 1601196873

 

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

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

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

|   0 | SELECT STATEMENT  |      |   201K|    17M|   622   (3)| 00:00:08 |

|   1 |  TABLE ACCESS FULL| T    |   201K|    17M|   622   (3)| 00:00:08 |

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

 

 

统计信息

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

          0  recursive calls

          0  db block gets

      16005  consistent gets

          0  physical reads

          0  redo size

   21626412  bytes sent via SQL*Net to client

     148115  bytes received via SQL*Net from client

      13432  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

     201452  rows processed

 

4、执行删除后,观察情况

SQL> delete t;

201452 rows deleted

 

SQL> commit;

Commit complete

 

分配情况:

SQL> exec dbms_stats.gather_table_stats('SYS','T',cascade => true);

 

PL/SQL procedure successfully completed

 

SQL> select segment_name, blocks, extents, INITIAL_EXTENT from dba_segments where wner='SYS' and segment_name='T';

 

SEGMENT_NA     BLOCKS    EXTENTS INITIAL_EXTENT

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

T                2816         37          65536

 

没有发生变化,如果这时候进行搜索。

SQL> select * from t;

未选定行

执行计划

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

Plan hash value: 1601196873

 

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

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

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

|   0 | SELECT STATEMENT  |      |     1 |   177 |   608   (1)| 00:00:08 |

|   1 |  TABLE ACCESS FULL| T    |     1 |   177 |   608   (1)| 00:00:08 |

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

 

 

统计信息

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

        387  recursive calls

          0  db block gets

       2812  consistent gets

          0  physical reads

          0  redo size

        992  bytes sent via SQL*Net to client

        374  bytes received via SQL*Net from client

          1  SQL*Net roundtrips to/from client

          2  sorts (memory)

          0  sorts (disk)

0    rows processed

 

结论:当我们手工将数据删除的时候,数据字段中对于表t的描述信息没有发生变化,仍然是37个extents,2816个block,也就意味着HWM没有变化。对比前后两个select的执行计划,发现虽然后者执行返回数据为0(统计信息预估),但是依然花费8s时间,CPU成本也达到608,没有节省时间。消耗CPU在consistent gets上有2812。

 

5、进行手工处理HWM

方法1:Move

可以使用alter table XX move的方法,对数据表中的数据进行重新排列,调整rowid位置。这样就可以降低HWM。

 

SQL> alter table t move;

 

Table altered

 

SQL>  exec dbms_stats.gather_table_stats('SYS','T',cascade => true);

 

PL/SQL procedure successfully completed

 

SQL> select segment_name, blocks, extents, INITIAL_EXTENT from dba_segments where wner='SYS' and segment_name='T';

 

SEGMENT_NA     BLOCKS    EXTENTS INITIAL_EXTENT

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

T                   8          1          65536

 

可以发现,数据字段中数据块的个数已经缩小,extents也只有1个。说明大小变化已经落实。

 

SQL> select * from t;

 

未选定行

 

 

执行计划

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

Plan hash value: 1601196873

 

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

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

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

|   0 | SELECT STATEMENT  |      |     1 |   177 |     2   (0)| 00:00:01 |

|   1 |  TABLE ACCESS FULL| T    |     1 |   177 |     2   (0)| 00:00:01 |

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

 

 

统计信息

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

        387  recursive calls

          0  db block gets

         52  consistent gets

          0  physical reads

          0  redo size

        992  bytes sent via SQL*Net to client

        374  bytes received via SQL*Net from client

          1  SQL*Net roundtrips to/from client

          2  sorts (memory)

          0  sorts (disk)

          0  rows processed

 

执行成本上,消耗时间只有1s左右,CPU的消耗也只有2。而consistent gets从未调整前的2812下降到了调整后的52。

 

 

方法2:shrink space

在10g,推出了收缩空间的方法,也可以起到HWM降低的效果。

SQL> alter table t enable row movement;

SQL> alter t shrink space;

 

 

方法3:truncate重建表

如果该表是一个空表,那么最简单的方法就是用truncate对表进行重建,这样HWM,都会重新确立结构。

SQL> truncate table t;

Table truncated

 

综上所述:Oracle的HWM机制,在管理数据表的方面上是存在一些问题的。对于一些数据量变化巨大的数据表来说,周期性的运维任务可能是最好的解决方法。HWM通常都需要手动的进行手动降低,来提高效率,避免大面积的查找。

但是,对于走索引的搜索而言,由于HWM引起的这种效率的降低应该是有限的。因为索引对应的rowid是没有变化,那么查询所遍历数据块的个数是不会发生变化的。

 

修改HWM的方法大体有三个:

1、  Move方法,Move是直接对于行进行重新排列的方法,可靠程度比较好。在有分区的时候,还是要指定分区。还可以实现segment对象的定向移动功能。问题在于因为move后,数据的rowid会发生变化,对应的index需要重新rebuild;

2、  Shrink space方法,是10g中新推出的功能,对于segment和tablespace都有尚可的效果。shrink space的操作是有前提的,首先启动了row movement,其次是对象所在数据表空间不能是自动管理段的。;

3、  Truncate命令,直接重新构建数据表;

 

猜你喜欢

转载自blog.csdn.net/thy822/article/details/80269668
今日推荐