[Oracle]高水位标记(HWM)

Posted 满格

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Oracle]高水位标记(HWM)相关的知识,希望对你有一定的参考价值。

(一)高水位标记(High Water Mark,HWM)的概念

所谓高水位标记,是指一个已经分配的段中,已经使用的空间与未使用的空间的分界线。在表的使用过程中,随着数据的不断增多(insert),HWM不断向数据段未使用部分方向移动,而在删除数据(delete)的过程中,HWM并不会向反方向移动,即使删除全部数据,HWM依然不会改变。但是如果使用了truncate命令,则表的HWM会被重置为0。

image_thumb1

   图1.segment

 

(二)高水位标记的影响

  • 全表扫描要读出直到HWM标记的所有的属于该表的数据块(used space),即使该表中没有任何数据;
  • 即使HWM下有空闲的数据块,如果在插入数据时使用了append参数,则在插入数据时使用HWM以上的数据块,HWM会自动增大。

 

(三)如何知道一个表的HWM

1.首先对表进行分析

SQL > ANALYZE TABLE table_name ESTIMATE/COMPUTE STATISTICS;

2.查看水线

SELECT
    blocks,                 --该表曾经使用过的数据块的数目,即水线
    empty_blocks,            --代表分配给该表,但是在水位线以上的数据块,即从来没有使用过的数据块
    num_rows
FROM 
    user_tables
WHERE
    table_name = ‘table_name’;

*注:在Oracle 11g中,收集数据库对象信息的最好方法,不再是使用带ESTIMATE或COMPUTE的ANALYSE语句,而是使用最新的DBMS_STATS包。但是,如果要收集数据库对象存储格式的有效性以及收集表与簇中的行迁移、行链接情况,还得使用ANALYSE。

(四)Oracle表段中的高水位线

每个Oracle数据块在ASSM段中都属于下面的一种状态:

  • 高水位线以上

         这些块未被格式化且从来没有被使用过。

  • 高水位线以下(3类)

         --已经分配,但是未格式化和未使用;

         --格式化,且含有数据;

         --格式化,不含有数据,因为数据被删除了。

(1)在创建表的时候,HWM位于segment左边的起始处,因为没有数据插入,segment中全部的block未被格式化和从未被使用。

image_thumb4

(2)假设一个事务将行数据插入到segmnet中,数据库必须分配一组数据块去保存行信息,被分配的数据块全在HWM之下,数据库格式化一个位图块来保存元数据,但是没有指定是哪一个数据块。

image_thumb7

在HWM以下的数据块是被分配的,在HWM以上的数据块从未被分配和格式化。当insert时,数据可以写到有可用空间的任何块中。low HWM以下的部分,所有的块都被格式化,因为它们要么含有数据,要么以前包含数据。

(3)当insert时,数据库选择在HWM和low HWM之间的任意一个块进行写入,或者是HWM以下的一个有空闲空间的块进行写入。下图中,在Low HWM与HWM之间已写满块的两侧的数据块还未格式化。

image_thumb10

(4)low HWM对于全表扫描是非常重要的。因为HWM以下的数据块只有在要使用时才格式化,有一些块还未格式化。基于这个原因,数据库会去bitmap块查询low HWM的位置,然后会去读low HWM以下的全部数据块,因为数据库已经知道这些块全部被格式化了,对于在low HWM和HWM之间的数据块,数据库会挑选那些已经格式化了的数据块进行读操作。

(5)假设一个新的事务进行插入操作,但是bitmap指示目前在HWM以下已经没有足够的空间了,数据库会向右移动该segment的HWM,分配一组新的未格式化的数据块。

image_thumb13  

 

(五)降低HWM

降低HWM可以使用rebuild、truncate、shrink、move等操作。

(5.1)SHRINK

shrink技术是一种段收缩技术,可将表与索引高水位以下的碎片进行有效压缩,并将高水位进行回退。

使用方法如下:

step1. 启用行移动

SQL > ALTER TABLE table_name ENABLE ROW MOVEMENT;

step2. 压缩数据及下调HWM

SQL > ALTER TABLE table_name SHRINK SPACE CASCADE; --压缩表及相关数据段并下调HWM

SQL > ALTER TABLE table_name SHRINK SPACE COMPACT; --只压缩数据不下调HWM

SQL > ALTER TABLE table_name SHRINK SPACE;         --下调HWM

即该技术可以一次性压缩数据及下调HWM,也可以分两个阶段进行,第一阶段:在业务高峰,只压缩数据不下调HWM,第二阶段:在业务空闲时,下调HWM。

(5.2)MOVE Tablespace

语法为:

SQL > ALTER TABLE table_name MOVE TABLESPACE tablepsace_name;

需要注意:

--MOVE后不跟参数也行,不跟参数还是原来的表空间;

--MOVE后需要重建索引;

--如果以后还要网表里插入数据,没必要MOVE。MOVE释放出来的空间,只能这个表使用,其它的表或者segment无法使用。

(5.3)CTAS技术

即重建表技术。

SQL > CREATE TABLE new_table_name 
AS
SELECT * FROM old_table_name;                          --将表的数据写入到一张新的表里

SQL > DROP TABLE old_table_name;                       --删除旧表

SQL > RENAME table new_table_name TO old_table_name    --将新表名更改为旧表名

(5.4)EXP/IMP或EXPDP/IMPDP技术

与CTAS技术相当。

(5.5)DEALLOCATE技术

利用DEALLOCATE技术可以回收HWM以上从未使用过的数据块。语法如下

SQL > ALTER TABLE table_name DEALLOCATE UNUSED [KEEP integer];         --回收表段HWM以上的空间

SQL > ALTER INDEX index_name DEALLOCATE UNUSED [KEEP integer];         --回收索引段HWM以上的空间

 

(六)MOVE与SHRINK的区别

 

MOVE

SHRINK(仅对ASSM有效)

本质

move实际上是block级别的数据块拷贝,对表进行move后,该表所在blockid会发生改变数据的rowid自然也会发生改变,但是数据在table中的存储顺序并没有发生改变

shrink是对行数据进行移动。对表进行shrink后,部分行数据的rowid发生了变化,而table所位于的block区域的位置却没有发生变化。

重建索引

需要重建

可以通过cascade关键字重建

TM(exclusive)

TM(SX)

空间要求

需要有原表大的空闲空间

不需要额外的空间

效果

压缩后会回收空间

压缩后会回收空间

详细例子见下面测试。

 

  (七)高水位问题测试

(8.1)测试目的:

      1.了解Oracle统计信息的概念;

      2.测试使用delete与truncate删除数据对HWM的影响[主要目的]

(8.2)主要步骤

(1)创建表test01

create table test01
(
  id number,
  name varchar(15)
);

  这个时候,去查看表与段的参数

SQL> select
  2    dt.table_name,
  3    dt.blocks,
  4    dt.empty_blocks
  5  from
  6    dba_tables dt
  7  where
  8    dt.table_name = \'TEST01\';
 
TABLE_NAME                         BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
TEST01                                  0            0
 
SQL> 
SQL> select
  2    ds.segment_name,
  3    ds.segment_type,
  4    ds.header_file,
  5    ds.header_block,
  6    ds.bytes,
  7    ds.blocks,
  8    ds.extents
  9  from
 10    dba_segments ds
 11  where
 12    ds.segment_name = \'TEST01\';
 
SEGMENT_NAME  SEGMENT_TYPE       HEADER_FILE HEADER_BLOCK      BYTES     BLOCKS    EXTENTS
------------- ------------------ ----------- ------------ ---------- ---------- ----------
  
SQL> exec dbms_stats.gather_table_stats(\'LIJIAMAN\',\'TEST01\');
 
PL/SQL procedure successfully completed
 
SQL> 
SQL> select
  2    dt.table_name,
  3    dt.blocks,
  4    dt.empty_blocks
  5  from
  6    dba_tables dt
  7  where
  8    dt.table_name = \'TEST01\';
 
TABLE_NAME                         BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
TEST01                                  0            0
 
SQL> 
SQL> select
  2    ds.segment_name,
  3    ds.segment_type,
  4    ds.header_file,
  5    ds.header_block,
  6    ds.bytes,
  7    ds.blocks,
  8    ds.extents
  9  from
 10    dba_segments ds
 11  where
 12    ds.segment_name = \'TEST01\';
 
SEGMENT_NAME  SEGMENT_TYPE     HEADER_FILE HEADER_BLOCK      BYTES     BLOCKS    EXTENTS
------------- -------------    ----------- ------------ ----------     ---------- ----------
 

通过以上结构可以看出,我在创建表后,去查看表信息,发现表拥有的blocks=0,以为是统计信息的问题,使用dbms_stats去重新收集表的基础信息,结果依然相同。然后查看该表段的信息,发现这个段并不存在。可以说明,数据库在创建表后,只存储了表的基本结构信息,只有在插入数据的时候,才会去分配区。

此时由于未分配数据块,也就不存在高水位线的问题。

(2)我们往test01里面插入10000条数据

SQL> declare
  2    i number :=1;
  3  begin
  4    loop
  5      if i > 10000
  6      then
  7        exit;
  8      end if;
  9      insert into test01 values(i,\'euvcg\');
 10      i:=i+1;
 11    end loop;
 12    commit;
 13  end;
 14  /
 
PL/SQL procedure successfully completed

 

再去看一下表与段的统计信息

SQL> select
  2    dt.table_name,
  3    dt.blocks,
  4    dt.empty_blocks
  5  from
  6    dba_tables dt
  7  where
  8    dt.table_name = \'TEST01\';
 
TABLE_NAME  BLOCKS     EMPTY_BLOCKS
----------- ---------- ------------
TEST01      0            0
 
SQL> 
SQL> select
  2    ds.segment_name,
  3    ds.segment_type,
  4    ds.header_file,
  5    ds.header_block,
  6    ds.bytes,
  7    ds.blocks,
  8    ds.extents
  9  from
 10    dba_segments ds
 11  where
 12    ds.segment_name = \'TEST01\';
 
SEGMENT_NAME SEGMENT_TYPE       HEADER_FILE HEADER_BLOCK      BYTES     BLOCKS    EXTENTS
------------ ------------------ ----------- ------------ ---------- ---------- ----------
TEST01       TABLE                        6          162     262144         32          4

 

表test01的blocks依然为0,我们使用dbms_stats重新收集统计信息,

SQL> exec dbms_stats.gather_table_stats(\'LIJIAMAN\',\'TEST01\');
 
PL/SQL procedure successfully completed

SQL> select
  2    dt.table_name,
  3    dt.blocks,
  4    dt.empty_blocks
  5  from
  6    dba_tables dt
  7  where
  8    dt.table_name = \'TEST01\';
 
TABLE_NAME                         BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
TEST01                                 28            0

SQL> select
  2    ds.segment_name,
  3    ds.segment_type,
  4    ds.header_file,
  5    ds.header_block,
  6    ds.bytes,
  7    ds.blocks,
  8    ds.extents
  9  from
 10    dba_segments ds
 11  where
 12    ds.segment_name = \'TEST01\';
 
SEGMENT_NAME SEGMENT_TYPE       HEADER_FILE HEADER_BLOCK      BYTES     BLOCKS    EXTENTS
------------ ------------------ ----------- ------------ ---------- ---------- ----------
TEST01       TABLE                        6          162     262144         32          4

收集统计信息后,我们对段进行分析,插入10000条数据,oracle一共分配了4个区,每个区包含8个数据块,每个数据块大小为8KB。此时,表的统计数据已经有了,但是表的Blocks与段的Blocks数量不同,这是什么引起的呢?通过查看两个blocks栏位的定义,可以看到:

dba_tables.blocks:该表已经使用的数据块的数量(Number of used data blocks in the table);

dba_segments.blocks:该段中数据块的总数(Size, in Oracle blocks, of the segment)。

也就是说还有4个数据块还未使用。

可以使用ANALYZE进行统计

SQL> analyze table TEST01 compute statistics;
 
Table analyzed
 
SQL> 
SQL> select
  2    dt.table_name,
  3    dt.blocks,
  4    dt.empty_blocks
  5  from
  6    dba_tables dt
  7  where
  8    dt.table_name = \'TEST01\';
 
TABLE_NAME                         BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
TEST01                                 28            4

经过分析,dba_tables统计的数据块与dba_segments统计的数据块数量相同了。

此时的高水位线应该如下:

image_thumb2

(3)删除test01里面的全部数据,重新统计信息,发现数据块并没有被回收释放。这些数据块(dba_tables.blocks=28)曾经拥有过数据,但是现在数据已被删除。

SQL> delete from test01;
 
10000 rows deleted
 
SQL> commit;
 
Commit complete
 
SQL> analyze table test01 compute statistics;
 
Table analyzed
 
SQL> 
SQL> select
  2    dt.table_name,
  3    dt.blocks,
  4    dt.empty_blocks
  5  from
  6    dba_tables dt
  7  where
  8    dt.table_name = \'TEST01\';
 
TABLE_NAME                         BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
TEST01                                 28            4
SQL> select
  2    ds.segment_name,
  3    ds.segment_type,
  4    ds.header_file,
  5    ds.header_block,
  6    ds.bytes,
  7    ds.blocks,
  8    ds.extents
  9  from
 10    dba_segments ds
 11  where
 12    ds.segment_name = \'TEST01\';
 
SEGMENT_NAME  SEGMENT_TYPE       HEADER_FILE HEADER_BLOCK      BYTES     BLOCKS    EXTENTS
------------- ------------------ ----------- ------------ ---------- ---------- ----------
TEST01        TABLE                        6          162     262144         32          4

此时的高水位线应该如下:

image_thumb3

黑色(used space)里面目前并没有数据,它仅仅代表曾经被使用过,白色(unused space)代表这些块已经分配给了test01段,但是还未使用过。

(4)测试完了delete,接下来测试truncate

SQL> truncate table test01;
 
Table truncated
 
--truncate后直接查询,发现表的统计信息依然未变化,而段的数据块已经回收了
SQL> select
  2    dt.table_name,
  3    dt.blocks,
  4    dt.empty_blocks
  5  from
  6    dba_tables dt
  7  where
  8    dt.table_name = \'TEST01\';
 
TABLE_NAME                         BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
TEST01                                 28            4
SQL> select
  2    ds.segment_name,
  3    ds.segment_type,
  4    ds.header_file,
  5    ds.header_block,
  6    ds.bytes,
  7    ds.blocks,
  8    ds.extents
  9  from
 10    dba_segments ds
 11  where
 12    ds.segment_name = \'TEST01\';
 
SEGMENT_NAME   SEGMENT_TYPE       HEADER_FILE HEADER_BLOCK      BYTES     BLOCKS    EXTENTS
-------------- ------------------ ----------- ------------ ---------- ---------- ----------
TEST01         TABLE                        6          162      65536          8          1
 
--执行dbms_stats重新收集统计信息,发现表的blocks已经为0,但是表的blocks与段的blocks并不相等
SQL> exec dbms_stats.gather_table_stats(\'LIJIAMAN\',\'TEST01\');
 
PL/SQL procedure successfully completed
 
SQL> select
  2    dt.table_name,
  3    dt.blocks,
  4    dt.empty_blocks
  5  from
  6    dba_tables dt
  7  where
  8    dt.table_name = \'TEST01\';
 
TABLE_NAME                         BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
TEST01                                  0            4
SQL> select
  2    ds.segment_name,
  3    ds.segment_type,
  4    ds.header_file,
  5    ds.header_block,
  6    ds.bytes,
  7    ds.blocks,
  8    ds.extents
  9  from
 10    dba_segments ds
 11  where
 12    ds.segment_name = \'TEST01\';
 
SEGMENT_NAME  SEGMENT_TYPE       HEADER_FILE HEADER_BLOCK      BYTES     BLOCKS    EXTENTS
------------- ------------------ ----------- ------------ ---------- ---------- ----------
TEST01        TABLE                        6          162      65536          8          1
 
--再使用ANALYZE进行分析,表的empty blocks为8,与段的blocks相等
SQL> analyze table test01 compute statistics;
 
Table analyzed
  
SQL> select
  2    dt.table_name,
  3    dt.blocks,
  4    dt.empty_blocks
  5  from
  6    dba_tables dt
  7  where
  8    dt.table_name = \'TEST01\';
 
TABLE_NAME                         BLOCKS EMPTY_BLOCKS
------------------------------ ---------- ------------
TEST01                                  0            8
SQL> select
  2    ds.segment_name,
  3    ds.segment_type,
  4    ds.header_file,
  5    ds.header_block,
  6    ds.bytes,
  7    ds.blocks,
  8    ds.extents
  9  from
 10    dba_segments ds
 11  where
 12    ds.segment_name = \'TEST01\';
 
SEGMENT_NAME   SEGMENT_TYPE       HEADER_FILE HEADER_BLOCK      BYTES     BLOCKS    EXTENTS
-------------- ------------------ ----------- ------------ ---------- ---------- ----------
TEST01         TABLE                        6          162      65536          8          1

可以发现,truncate后,表的空间已经回收,但是并不等于0,而是一个extent的大小。此时高水位线为:

image_thumb6

至于8个数据块是否有一个被使用(segment header),由于个人能力有限,无法进行分析 –_-

(8.3)结论:通过测试,delete无法降低高水位线,truncate可以。

 

(八)shrink与move测试

(9.1)测试目的:

1.测试shrink与move的区别,主要是第(七)点列出的区别

(9.2)测试步骤

(1)创建测试表,插入数据,分析表,查看统计信息

--创建表
SQL> create table test02
  2  (
  3    id number,
  4    name varchar(15)
  5  );
 
Table created
 
--插入1000万条数据
SQL> declare
  2    i number :=1;
  3  begin
  4    loop
  5      if i > 10000000
  6      then
  7        oracle 高水位线详解

oracle 高水位线详解

高水位线

oracle 高水位线详解

Oracle 高水位(HWM)回收原理及操作方法

第十四章:高水位线