Oracle中数据压缩功能小结

Oracle数据库的压缩功能,大家听到这个词,一定就想到了节省硬盘空间吧。没错,它就是用来干这个的,但是向外延伸一下,它不仅可以节省硬盘空间,更可以节省带宽以及内存的使用(cpu有一点点损耗因为需要更多的计算取值操作,但是鉴于大多数环境下cpu都有空闲,所以也可以忽略了)。


Basic compression这个功能从9i开始就有提供,11gr1开始提供了新的压缩功能,即OLTP compressionBasic compression是包括在EE版本中的,不需要额外收费,但是OLTP compression则需要额外的Oracle Advanced Compression option license。高级?想用?掏钱吧。


下面的图片能够帮助你简单了解压缩块的原理:


Oracle中数据压缩功能小结_第1张图片



简单来说,就是在block中会存有特殊的数据结构,symbol table


Symbol table dump出来的内容如下所示:

  1. tab 0, row 0, @0x1da4
  2. tl: 7 fb: --H-FL-- lb: 0x0  cc: 12
  3. col  0: *NULL*
  4. col  1: [ 5]  56 41 4c 49 44
  5. col  2: [ 1]  4e
  6. col  3: *NULL*
  7. col  4: [ 1]  4e
  8. col  5: [ 1]  4e
  9. col  6: [ 3]  53 59 53
  10. col  7: [ 7]  78 6f 0a 1e 0d 39 19
  11. col  8: [19]  32 30 31 31 2d 31 30 2d 33 30 3a 31 32 3a 35 36 3a 32 34
  12. col  9: [ 2]  c1 05
  13. col 10: [ 5]  49 4e 44 45 58
  14. col 11: [ 7]  78 6f 0a 1e 0d 39 19
  15. bindmp: 00 2a 0c 15 20 24 22
复制代码
真正的数据内容如下所示:
  1. tab 1, row 0, @0x1d5e
  2. tl: 16 fb: --H-FL-- lb: 0x0  cc: 15
  3. col  0: *NULL*
  4. col  1: [ 5]  56 41 4c 49 44
  5. col  2: [ 1]  4e
  6. col  3: *NULL*
  7. col  4: [ 1]  4e
  8. col  5: [ 1]  4e
  9. col  6: [ 3]  53 59 53
  10. col  7: [ 7]  78 6f 0a 1e 0d 39 18
  11. col  8: [19]  32 30 31 31 2d 31 30 2d 33 30 3a 31 32 3a 35 36 3a 32 33
  12. col  9: [ 2]  c1 02
  13. col 10: [ 5]  54 41 42 4c 45
  14. col 11: [ 7]  78 6f 0a 1e 0e 10 01
  15. col 12: [ 2]  c1 03
  16. col 13: [ 5]  49 43 4f 4c 24
  17. col 14: [ 2]  c1 15
  18. bindmp: 2c 00 05 0a 05 31 1e cd 49 43 4f 4c 24 ca c1 15
复制代码
有点类似于 clustertable block 结构。 简单来说就是只将block重复的内容存放一份到symbol table中,真正的表row piece引用存放在symbol table中的重复的内容。
因为 Basiccompression OLTPcompresssion 都是块内压缩,所以就算拥有重复的内容,但是如果不在同一个块内存放,还是无法达到压缩的效果。
其原理先介绍到这,如果想要深入研究,可以参考老熊的 blog
Oracle 压缩表数据块格式解析 http://www.laoxiong.net/dissect_compressed_block_part1.html
概念补完了,下面来简单介绍一下表压缩的几种类型:





1. 不压缩


别心急,我觉得它应该算是表压缩的类型之一,为了严谨一些,这里把它列为第一位。关键字 nocompress 。意思就是不压缩。默认创建表的时候会根据你的表空间的 default 属性来判断是否压缩。如果你的表空间定义为 default compress ,那么在此表空间创建非压缩表的时候则需要指定 nocompress 例句:
  1. create table t(id number) nocompress ;
复制代码

相关 SQL (留给大家自己测试):
  1. alter tablespace users default compress ;
  2. select tablespace_name , compress_for from dba_tablespaces where tablespace_name='USERS' ;
  3. create table   ttt (id number) tablespace users ;
  4. create table   ttt2 (id number) tablespace users nocompress ;
  5. select table_name , COMPRESS_FOR from user_tables where table_name in ('TTT','TTT2') ;
  6. alter tablespace users default nocompress ;
  7. select tablespace_name , compress_for from dba_tablespaces where tablespace_name='USERS' ;
复制代码
2. Basic compression
  • 要点1:数据压缩只在direct path load的时候才会生效,对于普通的dml语句insert、update不会发生压缩。
  • 要点2:压缩的时候为了保证最大程度的压缩,会自动设置pctfree为0(不是强制的,也可以在建表的时候设置pctfree为其他值)。
例句:
  1. create table t(id number) compress ;
  2. create table t(id number) compress basic ;
复制代码
3. OLTP compression
  • 要点1:不像Basic compression,OLTP compression会对所有的dml生效,所以适用于OLTP系统。
  • 要点2:自动设置pctfree为10,也可以自行设置。
  • 要点3:oracle数据库内部的压缩动作不是在所有dml操作中都产生,只有当新的block中的数据存放到达了厥值的时候才会引发块内的压缩操作。
这里重点描述一下第三个要点:
oracle 数据库压缩数据块在批量模式下 ,而不是每个更改数据的操作都会引发压缩操作。
block中保留未压缩的数据达到了一个厥值的时候会引发块内的压缩操作。
当一个事务导致在block中的数据达到了厥值,所有的block中的内容就会被压缩。然后,更多的数据加入到块中,再一次达到厥值,整个block会重新压缩,以达到最大程度的压缩级别。这个过程会一直重复,直到oracle 数据库确定无法再从压缩上获得更高的效益。只有某些触发压缩条件的的事务会经历轻微的压缩开销。
所以多数oltp事务作用在压缩的块上面,会和未压缩的表上拥有相同的性能。
只有那些比较倒霉的操作会引发块内的压缩动作。


这里的详细内容可以参考aco白皮书。

例句:
  1. create table t(id number) compress for oltp ;
复制代码
4. HCC(Hybrid Columnar Compression)


这个特性只为Exadata一体机环境下的Oracle数据库提供。可以分为4个级别,即:
  •          Query low
  •          Query high
  •          Archive low
  •          Archive high
比较适用于更改不频繁或者readonly的表。根据上面排列的级别,相应的压缩比越来越高,看到介绍,竟然最多可以达到30倍,确实令人咋舌(当然这应该是最好的情况)。原理简单描述:

Oracle中数据压缩功能小结_第2张图片


不像传统的数据存储,一行中所有的信息都存放在一起。而是使用CUCompressionUnit的结构来存储数据,将一组(很多行)行中每列的信息存放在一起,然后执行压缩操作。可以节省更多的空间。(原来只是块内引用,现在是多块内引用,肯定节省更多的空间)但是同样的,由于这种存储结构,在发生更新1行的数据的时候,会连带的锁定一个Logical Compression Unit中的所有行。


我不是OracleDatabase的销售,所以关于HCC的内容就介绍到这里。

最后说一下关于压缩的几点限制:

1.      compress Basiccompress for oltp 不支持超过255列的信息。(真心蛋疼,难道是因为超过255列的row会跨越多个row piece的原因?)

2.      BasicFile LOBs 不支持压缩,SecureFile LOBs支持压缩。(新东西带来新bug

3.      使用compress basic的时候无法drop column,但是对于compress for oltp以及hcc中的各种压缩格式是允许的。

4.      不支持索引组织表。

5.      不支持外部表或者cluster table(直接说只支持堆积表得了)。


HCC的限制内容不描述了。‘



下面附上show_spaceitpub改良版)脚本:

  1. CREATE OR REPLACE PROCEDURE show_space(p_segname_1   IN VARCHAR2,
  2.                                        p_type_1      IN VARCHAR2 DEFAULT 'TABLE',
  3.                                        p_space       IN VARCHAR2 DEFAULT 'AUTO',
  4.                                        p_analyzed    IN VARCHAR2 DEFAULT 'N',
  5.                                        p_partition_1 IN VARCHAR2 DEFAULT NULL,
  6.                                        p_owner_1     IN VARCHAR2 DEFAULT USER)
  7.   AUTHID CURRENT_USER AS
  8.   p_segname   VARCHAR2(100);
  9.   p_type      VARCHAR2(30);
  10.   p_owner     VARCHAR2(30);
  11.   p_partition VARCHAR2(50);

  12.   l_unformatted_blocks NUMBER;
  13.   l_unformatted_bytes  NUMBER;
  14.   l_fs1_blocks         NUMBER;
  15.   l_fs1_bytes          NUMBER;
  16.   l_fs2_blocks         NUMBER;
  17.   l_fs2_bytes          NUMBER;
  18.   l_fs3_blocks         NUMBER;
  19.   l_fs3_bytes          NUMBER;
  20.   l_fs4_blocks         NUMBER;
  21.   l_fs4_bytes          NUMBER;
  22.   l_full_blocks        NUMBER;
  23.   l_full_bytes         NUMBER;

  24.   l_free_blks          NUMBER;
  25.   l_total_blocks       NUMBER;
  26.   l_total_bytes        NUMBER;
  27.   l_unused_blocks      NUMBER;
  28.   l_unused_bytes       NUMBER;
  29.   l_LastUsedExtFileId  NUMBER;
  30.   l_LastUsedExtBlockId NUMBER;
  31.   l_LAST_USED_BLOCK    NUMBER;

  32.   PROCEDURE p(p_label IN VARCHAR2, p_num IN NUMBER) IS
  33.   BEGIN
  34.     DBMS_OUTPUT.put_line(RPAD(p_label, 40, '.') || p_num);
  35.   END;
  36. BEGIN
  37.   p_segname   := UPPER(p_segname_1);
  38.   p_owner     := UPPER(p_owner_1);
  39.   p_type      := p_type_1;
  40.   p_partition := UPPER(p_partition_1);

  41.   IF (p_type_1 = 'i' OR p_type_1 = 'I') THEN
  42.     p_type := 'INDEX';
  43.   END IF;

  44.   IF (p_type_1 = 't' OR p_type_1 = 'T') THEN
  45.     p_type := 'TABLE';
  46.   END IF;

  47.   IF (p_type_1 = 'tp' OR p_type_1 = 'TP') THEN
  48.     p_type := 'TABLE PARTITION';
  49.   END IF;

  50.   IF (p_type_1 = 'ip' OR p_type_1 = 'IP') THEN
  51.     p_type := 'INDEX PARTITION';
  52.   END IF;

  53.   IF (p_type_1 = 'c' OR p_type_1 = 'C') THEN
  54.     p_type := 'CLUSTER';
  55.   END IF;

  56.   DBMS_SPACE.UNUSED_SPACE(segment_owner             => p_owner,
  57.                           segment_name              => p_segname,
  58.                           segment_type              => p_type,
  59.                           partition_name            => p_partition,
  60.                           total_blocks              => l_total_blocks,
  61.                           total_bytes               => l_total_bytes,
  62.                           unused_blocks             => l_unused_blocks,
  63.                           unused_bytes              => l_unused_bytes,
  64.                           LAST_USED_EXTENT_FILE_ID  => l_LastUsedExtFileId,
  65.                           LAST_USED_EXTENT_BLOCK_ID => l_LastUsedExtBlockId,
  66.                           LAST_USED_BLOCK           => l_LAST_USED_BLOCK);

  67.   IF p_space = 'MANUAL' OR (p_space <> 'auto' AND p_space <> 'AUTO') THEN
  68.     DBMS_SPACE.FREE_BLOCKS(segment_owner     => p_owner,
  69.                            segment_name      => p_segname,
  70.                            segment_type      => p_type,
  71.                            partition_name    => p_partition,
  72.                            freelist_group_id => 0,
  73.                            free_blks         => l_free_blks);
  74.   
  75.     p('Free Blocks', l_free_blks);
  76.   END IF;

  77.   p('Total Blocks', l_total_blocks);
  78.   p('Total Bytes', l_total_bytes);
  79.   p('Unused Blocks', l_unused_blocks);
  80.   p('Unused Bytes', l_unused_bytes);
  81.   p('Last Used Ext FileId', l_LastUsedExtFileId);
  82.   p('Last Used Ext BlockId', l_LastUsedExtBlockId);
  83.   p('Last Used Block', l_LAST_USED_BLOCK);

  84.   /*IF the segment is analyzed */
  85.   IF p_analyzed = 'Y' THEN
  86.     DBMS_SPACE.SPACE_USAGE(segment_owner      => p_owner,
  87.                            segment_name       => p_segname,
  88.                            segment_type       => p_type,
  89.                            partition_name     => p_partition,
  90.                            unformatted_blocks => l_unformatted_blocks,
  91.                            unformatted_bytes  => l_unformatted_bytes,
  92.                            fs1_blocks         => l_fs1_blocks,
  93.                            fs1_bytes          => l_fs1_bytes,
  94.                            fs2_blocks         => l_fs2_blocks,
  95.                            fs2_bytes          => l_fs2_bytes,
  96.                            fs3_blocks         => l_fs3_blocks,
  97.                            fs3_bytes          => l_fs3_bytes,
  98.                            fs4_blocks         => l_fs4_blocks,
  99.                            fs4_bytes          => l_fs4_bytes,
  100.                            full_blocks        => l_full_blocks,
  101.                            full_bytes         => l_full_bytes);
  102.     DBMS_OUTPUT.put_line(RPAD('', 50, '*'));
  103.     DBMS_OUTPUT.put_line('Thesegment is analyzed');
  104.     p('0% -- 25% free spaceblocks', l_fs1_blocks);
  105.     p('0% -- 25% free spacebytes', l_fs1_bytes);
  106.     p('25% -- 50% free spaceblocks', l_fs2_blocks);
  107.     p('25% -- 50% free spacebytes', l_fs2_bytes);
  108.     p('50% -- 75% free spaceblocks', l_fs3_blocks);
  109.     p('50% -- 75% free spacebytes', l_fs3_bytes);
  110.     p('75% -- 100% free spaceblocks', l_fs4_blocks);
  111.     p('75% -- 100% free spacebytes', l_fs4_bytes);
  112.     p('Unused Blocks', l_unformatted_blocks);
  113.     p('Unused Bytes', l_unformatted_bytes);
  114.     p('Total Blocks', l_full_blocks);
  115.     p('Total bytes', l_full_bytes);
  116.   END IF;
  117. END;
复制代码




使用方法:


  1. _sys@FAKE> exec dexter.show_space('BASICC3','T','AUTO','Y') ;
  2. Total Blocks............................384
  3. Total Bytes.............................3145728
  4. Unused Blocks...........................18
  5. Unused Bytes............................147456
  6. Last Used Ext FileId....................4
  7. Last Used Ext BlockId...................584704
  8. Last Used Block.........................110
  9. Thesegment is analyzed
  10. 0% -- 25% free spaceblocks..............0
  11. 0% -- 25% free spacebytes...............0
  12. 25% -- 50% free spaceblocks.............0
  13. 25% -- 50% free spacebytes..............0
  14. 50% -- 75% free spaceblocks.............0
  15. 50% -- 75% free spacebytes..............0
  16. 75% -- 100% free spaceblocks............43
  17. 75% -- 100% free spacebytes.............352256
  18. Unused Blocks...........................0
  19. Unused Bytes............................0
  20. Total Blocks............................309
  21. Total bytes.............................2531328

  22. PL/SQL procedure successfully completed.

  23. _sys@FAKE> exec dexter.show_space('BASICC2') ;
  24. Total Blocks............................384
  25. Total Bytes.............................3145728
  26. Unused Blocks...........................54
  27. Unused Bytes............................442368
  28. Last Used Ext FileId....................4
  29. Last Used Ext BlockId...................584320
  30. Last Used Block.........................74
复制代码




小脚本:
  1. select table_name, compress_for, compression, PCT_FREE, PCT_USED
  2.   from user_tables
  3. where table_name in ('BASICC', 'BASICC2');
复制代码
注意,在创建compress 压缩表的时候指定pct_free的值,不然结果不严谨。 另外做测试的时候,大家可以放心使用
  1. create table basicc2 tablespace users compress as select * from dba_objects ;
复制代码
create table as select 的方式创建压缩表,因为它本身使用的是direct pathwrite/read  




你可能感兴趣的:(Oracle)