表压缩(compress)的原理

三月 31, 2008 – 4:03 下午

 最近写了几篇关于表压缩的文章,介绍了排序,日期等对压缩效率的影响。这些都是表面现象,是由表压缩的内部原理的外在表现。下面我们研究一下表压缩的内部原理。

选择最普通的测试用例,这样更具有代表性,使我们不会遗漏任何重要的部分。

SQL> create table sunwg compress as select * from dba_objects;

表已创建。

SQL> desc sunwg

 名称                                      是否为空? 类型

 —————————————– ——– ————–

 OWNER                                              VARCHAR2(30)

 OBJECT_NAME                                        VARCHAR2(128)

 SUBOBJECT_NAME                                     VARCHAR2(30)

 OBJECT_ID                                          NUMBER

 DATA_OBJECT_ID                                     NUMBER

 OBJECT_TYPE                                        VARCHAR2(19)

 CREATED                                            DATE

 LAST_DDL_TIME                                      DATE

 TIMESTAMP                                          VARCHAR2(19)

 STATUS                                             VARCHAR2(7)

 TEMPORARY                                          VARCHAR2(1)

 GENERATED                                          VARCHAR2(1)

 SECONDARY                                          VARCHAR2(1)

SQL> select rowid from sunwg where rownum < 2;

ROWID

——————

AAAM/WAAGAAAAAMAAA

SQL> select dbms_rowid.rowid_relative_fno(’AAAM/WAAGAAAAAMAAA’) from dual;

DBMS_ROWID.ROWID_RELATIVE_FNO(’AAAM/WAAGAAAAAMAAA’)

—————————————————

                                                  6

SQL> select dbms_rowid.rowid_block_number(’AAAM/WAAGAAAAAMAAA’) from dual;

DBMS_ROWID.ROWID_BLOCK_NUMBER(’AAAM/WAAGAAAAAMAAA’)

—————————————————

                                                 12

SQL> alter system dump datafile 6 block 12;

系统已更改。

由于转储文件比较大,仅仅贴出相关部分:

data_block_dump,data header at 0×7d22a7c

===============

tsiz: 0×1f80

hsiz: 0×302

pbl: 0×07d22a7c

bdba: 0×0180000c

     76543210

flag=-0——

ntab=2               //注意这里的ntab = 2,普通表应该是1

nrow=357             //表中一共存储了357条记录

frre=-1

fsbo=0×302

fseo=0×31e

avsp=0×1c

tosp=0×1c

    r0_9ir2=0×0

    mec_kdbh9ir2=0×24

                  76543210

    shcf_kdbh9ir2=———-

              76543210

    flag_9ir2=0-R—OC

        fcls_9ir2[6]={ 0 32768 32768 32768 32768 32768 }

        perm_9ir2[13]={ 5 11 0 12 10 6 7 9 8 1 2 4 3 }

这个是表压缩设计比较成功的地方。perm_9ir2是个配置序列,通过它可以将下面存储的字段顺序和数据字典表中的字段顺序建立联系。以上面这个来说,col[5]对应字典表中的第一个字段OWNER,col[11]对应字典表中的第二个字段OBJECT_NAME,依次类推,对照好的的结构大概是这样:

OWNER                     col[5]

OBJECT_NAME               col[11]

SUBOBJECT_NAME            col[0]

OBJECT_ID                 col[12]

DATA_OBJECT_ID            col[10]

OBJECT_TYPE               col[6]

CREATED                   col[7]

LAST_DDL_TIME             col[9]

TIMESTAMP                 col[8]

STATUS                    col[1]

TEMPORARY                 col[2]

GENERATED                 col[4]

SECONDARY                 col[3]

从这我们能看得出来在数据块中字段存储的顺序并不一定和数据字典中相同,oracle会有个自动调整,使的表压缩的效果达到最好。这也就解释了上篇文章中的问题,字段顺序为什么对表压缩的效果没有任何影响。

0×30:pti[0] nrow=73 offs=0

0×34:pti[1] nrow=284    offs=73

0×38:pri[0] offs=0×1dbb

……………………

0×300:pri[356]  offs=0×31e

block_row_dump:

tab 0, row 0, @0×1dbb

tl: 12 fb: –H-FL– lb: 0×0  cc: 6

col  0: *NULL*

col  1: [ 5]  56 41 4c 49 44

col  2: [ 1]  4e

col  3: [ 1]  4e

col  4: [ 1]  4e

col  5: [ 3]  53 59 53

bindmp: 00 04 06 ff 47 48 48 48 cb 53 59 53

tab 0, row 1, @0×1db1

tl: 10 fb: –H-FL– lb: 0×0  cc: 7

col  0: *NULL*

col  1: [ 5]  56 41 4c 49 44

col  2: [ 1]  4e

col  3: [ 1]  4e

col  4: [ 1]  4e

col  5: [ 3]  53 59 53

col  6: [ 5]  54 41 42 4c 45

bindmp: 00 0a 07 00 cd 54 41 42 4c 45

tab 0的这部分就代表表中的公共部分,也就是表中可以被压缩的部分。经过我的测试发现,这公共的部分col的顺序是递增的,也就是说不会出现col 1,col 3这种缺少中间字段的问题,这应该是为了保证压缩字段的准确性和解压缩的效率。另外一方面,被压缩的部分分成两类,一类是上面这样的多个字段的压缩,还有一部分是单个字段的压缩部分。至于这两个部分的分配情况完全是由oracle决定的,这和索引压缩有很大的不同,索引压缩的压缩部分是由人为决定,既然是人为决定就有可能出现很大的问题,而表压缩采取系统来判断,这样避免了人为的错误。单个字段的压缩部分如下:
tab 0, row 46, @0×1e9f

tl: 22 fb: –H-FL– lb: 0×0  cc: 1

col  0: [19]  32 30 30 35 2d 30 38 2d 33 30 3a 31 33 3a 35 30 3a 32 38

虽然在这我们看到的是col 0,但实际上这个字段具体对应数据字典的哪个字段完全是由压缩字段的顺序决定的。后面会看到相关的例子。

接下来就是真正存储表中记录的位置了,前面这些压缩基础部分是tab 0,而真正的数据是则是用tab 1来表示的。

tab 1, row 1, @0×1c9b

tl: 19 fb: –H-FL– lb: 0×0  cc: 13

bindmp: 2c 00 04 05 14 ca c1 2d cf 49 5f 55 53 45 52 31 ca c1 2d

实际上数据库存储就是bindmp后面这串字符,相比未压缩表中的数据来说,节省了大量的空间。那么解压缩的时候oracle是如何操作的呢,我们怎么解释这串复杂的字符呢?

不知道大家对普通表中的存储结构了解多少,它采取的字段长度+字段内容的存储方式。表压缩也是采用的这种方式。上面的字符串可以分解成这样:
2c 00 04 05 || 14 || ca || c1 2d || cf || 49 5f 55 53 45 52 31 || ca ||c1 2d

前两个字节2c和00具体不知道代表什么,不过对我们解压缩没有任何的影响。

第三个字节代表着这个条记录压缩后分成几个部分,上面这条记录分成4个部分,分别是14,c1 2d,49 5f 55 53 45 52 31和c1 2d

第四个字节让人很迷惑,很多资料都说是表示压缩了多少个字段,但在我的例子真显然是有问题的,我至少压缩了10个字段。我猜测这个字节应该指的是非压缩字段的起始位置,非压缩字段的起始于数据字典的第5个字段,也就是对应数据块中的col[10],说明从col[10]开始(包括col[10])都是非压缩的字段。

第五个字节开始就是真正数据的开始了,上面的14(十六进制)表示取压缩tab 0中的row 20。我们查询tab 0中的row 20,信息如下:

tab 0, row 20, @0×1d3e

tl: 7 fb: –H-FL– lb: 0×0  cc: 10

col  0: *NULL*

col  1: [ 5]  56 41 4c 49 44

col  2: [ 1]  4e

col  3: [ 1]  4e

col  4: [ 1]  4e

col  5: [ 3]  53 59 53

col  6: [ 5]  49 4e 44 45 58

col  7: [ 7]  78 69 08 1e 0e 33 1a

col  8: [19]  32 30 30 35 2d 30 38 2d 33 30 3a 31 33 3a 35 30 3a 32 35

col  9: [ 7]  78 69 08 1e 0e 33 1a

这样我们就有了前10个字段的信息了。

根据第三个字节的04和第四个字节的05我们可以判断出接下来的3部分数据都是表中非压缩的部分,也就是col[10],col[11],col[12]。前面说过了这部分的存储方式是字段长度+字段内容,为ca || c1 2d || cf || 49 5f 55 53 45 52 31 || ca ||c1 2d。其中的ca,cf,ca都是表示字段的长度,具体对应关系如下:

字符表示 字段长度 字符表示 字段长度 字符表示 字段长度
c9 1 d1 9 d9 17
ca 2 d2 10 da 18
cb 3 d3 11 db 19
cc 4 d4 12 dc 20
cd 5 d5 13 dd 21
ce 6 d6 14 de 22
cf 7 d7 15 df 23
d0 8 d8 16 e0 24

上表仅仅列出前24个对应关系,后面的依次类推。

有了上面这个对应关系我们就可以很好的进行拆分了。

下面在举个复杂一些的例子

bindmp: 2c 00 05 05 10 2a cb c2 03 54 d3 49 4e 44 53 55 42 50 41 52 54 24 cb c2 03 54

拆分后:2c 00 05 05 || 10 || 2a || cb || c2 03 54 || d3 || 49 4e 44 53 55 42 50 41 52 54 24 || cb  || c2 03 54

这个就是压缩表在物理数据块上的存储规则,这仅仅是表压缩的本质。至于我们如何利用这些知识来指导我们进行高效的表压缩呢,那将是另外一篇文章了。

  1. 3 Responses to “表压缩(compress)的原理”

  2. 孙工你太牛了,佩服得我五体投地,又如滔滔江水,一发不可收拾,如果我是美女我就嫁给你了,可惜我连女人都不是,哎

    By 蝈蝈 on 四 1, 2008

  3. 还好你不是女人
    如果你是女人
    肯定找不到男朋友

    By oratea on 四 1, 2008

  4. bindmp: 2c 00

    2c 00 的具体解释为:
    0010 1100 –H- FL– 对应与row的flag byte, 标识记录的开始结束/结束, 是否有行迁移/链接.
    这8位的具体定义如下:
    #define KDRHFK 0×80 Cluster Key
    #define KDRHFC 0×40 Clustered table member
    #define KDRHFH 0×20 Head piece of row
    #define KDRHFD 0×10 Deleted row
    #define KDRHFF 0×08 First data piece|
    #define KDRHFL 0×04 Last data piece
    #define KDRHFP 0×02 First column continues from Previous
    piece
    #define KDRHFN 0×01 Last column continues in Next piece

    后面的00对应于lock byte.
    0000 0000 lb:0×0 由于它的长度为一个字节, 也就是最多对应256, 同时可以对应于最多256个itl slot,也同时限制slot最多只能为256个(逻辑限制), 有可能由于空间的限制而无法达到256(每个itl 24个字节, 每个block 的itl的空间不能超过block的50%).

    By jametong on 九 22, 2009

Post a Comment