数据被压缩的原理在于,对于数据块里的重复值来说,只保留一份。保存在数据块里的压缩数据是自我包含的。也就是说,对于将压缩的数据进行解压来说,所有需要的信息都在数据块里了,而不需要参考数据块以外的其他数据。
数据块里的所有重复数据行和数据列都只存放一次,并放在数据块的开始部分,这块区域叫做数据块的symbol table。也就是说,同一个数据块里放了两个表的数据。这一点有点像class table。数据行里如果存在重复的值,则该重复值去掉,并在原来的位置上放了一个很短的引用,指向symbol table。除了数据块开始部分的symbol table以外,压缩数据块与普通的数据块没什么两样。注意,在9i里,压缩表在创建以后就不能修改结构的,而10g则没有这个限制了,但是不能对添加的列提供缺省值。
对于压缩数据块的访问来说,并没有很消耗资源的解压缩操作。这也就是说,在考虑是否要启用压缩时,并不需要考虑对于查询性能的影响。只能在进行批量数据加载的时候,完成压缩操作。在加载数据并压缩过程中,CPU资源的消耗会有50%的增长。
只有当数据批量插入或加载的时候完成压缩,这些操作包括:
·SQL*Loader的直接路径加载。
·CREATE TABLE … COMPRESS AS SELECT。
·并行insert或带有append提示的串行insert。你也可以发出ALTER TABLE…MOVE COMPRESS,从而将表里已有的数据进行压缩。
对于压缩表的update操作的效率极其低下,如果一个表要经常被update,则不应该对该表启用压缩。因为update时,需要先将数据解压,然后对解压后的数据进行更新,更新结束以后再次压缩。
除了LOB以外的数据类型都可以被压缩。另外,如果表里的列超过255个,则压缩被禁用。也提供对索引的压缩,包括B树索引和bitmap索引,以及IOT表。
来了解一下压缩表的内部结构。
SQL> create table t_seed as
2 select chr(trunc(dbms_random.value(65,67))) c1,chr(trunc(dbms_random.value(65,67))) c2,
3 trunc(dbms_random.value(0,2)) c1flag,trunc(dbms_random.value(0,2)) c2flag
4 from dba_objects where rownum<=1000;
表已创建。
SQL> create table t_comp_1 compress as
2 select decode(c1flag,1,lpad(c1,10,c1),null) as c1,
3 decode(c2flag,1,lpad(c2,10,c2),null) as c2 from t_seed;
表已创建。
SQL> select distinct dbms_rowid.rowid_relative_fno(rowid) as file#,
2 dbms_rowid.rowid_block_number(rowid) as block# from t_comp_1 order by file#,block#;
FILE# BLOCK#
---------- ----------
4 7604
4 7605
SQL> alter system dump datafile 4 block 7604;
系统已更改。
data_block_dump,data header at 0xcc19a7c
===============
tsiz: 0x1f80
hsiz: 0x5ce
pbl: 0x0cc19a7c
bdba: 0x01001db4
76543210
flag=-0------
ntab=2 这里的2说明该数据块里有2个表,一个symbol table,一个实际的表。
nrow=725
frre=-1
fsbo=0x5ce
fseo=0x1147
avsp=0x3d
tosp=0x3d
r0_9ir2=0x0
mec_kdbh9ir2=0x9 这里的9说明该数据块里重复值有9种情况。
76543210
shcf_kdbh9ir2=----------
76543210
flag_9ir2=--R----C
fcls_9ir2[3]={ 0 32768 32768 }
0x1c:pti[0] nrow=11 offs=0 这部分说明symbol table里有6条记录。
0x20:pti[1] nrow=717 offs=11 这部分说明该数据块里存放的实际表里的记录行数为717行。
0x24:pri[0] offs=0x1f61
0x26:pri[1] offs=0x1f5c
......................
tab 0, row 0, @0x1f61
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: *NULL*
col 1: *NULL*
bindmp: 00 a7 02 ff ff
tab 0, row 1, @0x1f5c
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: *NULL*
col 1: [10] 42 42 42 42 42 42 42 42 42 42
bindmp: 00 50 02 ff 0a
tab 0, row 2, @0x1f57
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: *NULL*
col 1: [10] 41 41 41 41 41 41 41 41 41 41
bindmp: 00 6c 02 ff 09
tab 0, row 3, @0x1f52
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [10] 42 42 42 42 42 42 42 42 42 42
col 1: *NULL*
bindmp: 00 55 02 0a ff
tab 0, row 4, @0x1f4d
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [10] 42 42 42 42 42 42 42 42 42 42
col 1: [10] 42 42 42 42 42 42 42 42 42 42
bindmp: 00 34 02 0a 0a
tab 0, row 5, @0x1f48
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [10] 42 42 42 42 42 42 42 42 42 42
col 1: [10] 41 41 41 41 41 41 41 41 41 41
bindmp: 00 22 02 0a 09
tab 0, row 6, @0x1f43
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [10] 41 41 41 41 41 41 41 41 41 41
col 1: *NULL*
bindmp: 00 5b 02 09 ff
tab 0, row 7, @0x1f3e
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [10] 41 41 41 41 41 41 41 41 41 41
col 1: [10] 42 42 42 42 42 42 42 42 42 42
bindmp: 00 2c 02 09 0a
tab 0, row 8, @0x1f39
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [10] 41 41 41 41 41 41 41 41 41 41
col 1: [10] 41 41 41 41 41 41 41 41 41 41
bindmp: 00 38 02 09 09
tab 0, row 9, @0x1f66
tl: 13 fb: --H-FL-- lb: 0x0 cc: 1
col 0: [10] 41 41 41 41 41 41 41 41 41 41
bindmp: 00 06 d2 41 41 41 41 41 41 41 41 41 41
tab 0, row 10, @0x1f73
tl: 13 fb: --H-FL-- lb: 0x0 cc: 1
col 0: [10] 42 42 42 42 42 42 42 42 42 42
bindmp: 00 06 d2 42 42 42 42 42 42 42 42 42 42
以上tab 0的部分就是所谓的symbol table里所存放的记录了。可以看到一共有11条记录。实际就是对c1和c2这两列所存在的值的穷举,如下所示:
|
可能的情况 |
C1列 |
C2列 |
|
1 |
AAAAAAAAAA |
Null |
|
2 |
BBBBBBBBBB |
Null |
|
3 |
Null |
AAAAAAAAAA |
|
4 |
Null |
BBBBBBBBBB |
|
5 |
AAAAAAAAAA |
AAAAAAAAAA |
|
6 |
AAAAAAAAAA |
BBBBBBBBBB |
|
7 |
BBBBBBBBBB |
AAAAAAAAAA |
|
8 |
BBBBBBBBBB |
BBBBBBBBBB |
|
9 |
Null |
Null |
而在该数据块里的每条记录就是对上面这9种情况的描述,不过它穷举出来的情况要更多点。
多出来的也就是最后两条cc为1的记录。也就是说有可能只压缩一列。在我们这个例子里,因为只有两个列,所以没有这种情况。如果表里的列很多,则有可能只压缩一列。
在tab 0里的每条记录里,记录了c1列和c2列的可能的取值,同时还记录了bindmp,这表示该种组合的一些元数据。比如对于symbol table里的row 4来说:
tab 0, row 4, @0x1f4d
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [10] 42 42 42 42 42 42 42 42 42 42
col 1: [10] 42 42 42 42 42 42 42 42 42 42
bindmp: 00 34 02 0a 0a
前两个字节表示该数据块里有多少条记录属于该组合。这里为十六进制的00 34,换算成十进制,也就是52。说明在该数据块里的717条记录当中,有52条记录的(c1,c2)=(‘BBBBBBBBBB’,’ BBBBBBBBBB’)。02表示压缩的列的个数为2个列。
我们来验证一下:
SQL> select count(*) from(
2 select c1,c2 from t_comp_1 where rownum<=717)
3 where c1='BBBBBBBBBB' and c2='BBBBBBBBBB';
COUNT(*)
----------
52
tab 0以后的部分就是tab 1,这部分就是数据块里实际存放的记录了。
tab 1, row 0, @0x1f34
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: [10] 42 42 42 42 42 42 42 42 42 42
col 1: *NULL*
bindmp: 2c 00 01 02 03
tab 1, row 1, @0x1f2f
tl: 5 fb: --H-FL-- lb: 0x0 cc: 2
col 0: *NULL*
col 1: *NULL*
bindmp: 2c 00 01 02 00
..................
转储的时候,进行了解压。把c1和c2的值都显示出来。实际物理存放的值是:
bindmp: 2c 00 01 02 03
..................
也就是对每条记录来说,只占用了5个字节。其中,2c 00应该表示symbol table的物理地址。02表示压缩了2个字段。03表示在symbol table里的记录行号。这里的03也就是symbol table里的row 3所记录的c1和c2的值。换句话说,该表里的第一条记录的(c1,c2)也就等于('BBBBBBBBBB', Null)。
这时再创建一个压缩表,该表里多加两个列:
SQL> create table t_comp_2 compress as
2 select decode(c1flag,1,lpad(c1,10,c1),null) as c1,rownum id1,
3 decode(c2flag,1,lpad(c2,10,c2),null) as c2,
4 chr(trunc(dbms_random.value(65,80))) c3 from t_seed;
表已创建。
SQL> select distinct dbms_rowid.rowid_relative_fno(rowid) as file#,
2 dbms_rowid.rowid_block_number(rowid) as block# from t_comp_2 order by file#,block#;
FILE# BLOCK#
---------- ----------
4 6900
4 6901
SQL> alter system dump datafile 4 block 6900;
系统已更改。
打开转储文件,可以看到下面的部分:
r0_9ir2=0x0
mec_kdbh9ir2=0x18
76543210
shcf_kdbh9ir2=----------
76543210
flag_9ir2=--R---OC
fcls_9ir2[4]={ 0 32768 32768 32768 }
perm_9ir2[4]={ 0 3 1 2 } 多出了这一行。
这一行非常重要,因为在压缩表中,oracle会修改列的排列顺序。非压缩表里,列在物理存放的时候,按照数据字典里记录的顺序,也就是col1、col2、col3....。比如:
SQL> desc t_comp_2
名称 是否为空? 类型
----------------------------------------- -------- ----------------------------
C1 VARCHAR2(2)
ID1 NUMBER
C2 VARCHAR2(2)
C3 VARCHAR2(2)
也就是说,正常表来说,在数据块里存放列时,会这样放:
col 0 XXXXXXX ->表示放的是C1列的值
col 1 XXXXXXX ->表示放的是ID1列的值
col 2 XXXXXXX ->表示放的是C2列的值
col 3 XXXXXXX ->表示放的是C3列的值
但是,在压缩表里,col 0已经不表示c1列的值了,col 1也不是ID1列的值。怎么对应呢,就是从这里的perm_9ir2[4]={ 0 3 1 2 }来对应。这里的0、3、1、2表示物理存储时的列的下标。也就是说col 0对应数据字典里的col 1,在这里也就是C1列;col 3对应ID1;col 1对应C2;col 2对应C3。
找到tab 1部分,比如:
tab 1, row 0, @0x1ea9
tl: 9 fb: --H-FL-- lb: 0x0 cc: 4
col 0: [10] 42 42 42 42 42 42 42 42 42 42 ->表示放的是C1列的值
col 1: *NULL* ->表示放的是C2列的值
col 2: [ 1] 49 ->表示放的是C3列的值
col 3: [ 2] c1 02 ->表示放的是ID1列的值
bindmp: 2c
00 03 03 12 28 ca c1 02 ->这里2c
00表示symbol
table的地址。而03
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/9842/viewspace-344546/
,如需转载,请注明出处,否则将追究法律责任。