您的位置:首页 > 数据库 > Oracle

Oracle 列顺序测试

2014-01-05 16:04 309 查看
列顺序测试

大家在做表设计的时候通常对表中列的排列顺序没有过多注意,但是其实越常用的列,它的位置越靠前,则查询速度越快。

因为每个block里面存储了row directory (每行数据在块中的位移地址)而没有存放column的位移所以只能根据column#通过扫描row header 以及 column data 来获取相应的列值越靠后的列,需要计算的越多

建立测试表col_test

declare

v_sql varchar2(4000) ;

begin

v_sql := 'create table col_test (' ;

for i in 1 .. 100 loop

v_sql := v_sql || 'id'||i||' number ,' ;

end loop ;

v_sql := substr(v_sql,1,length(v_sql)-1) || ') ' ;

execute immediate v_sql ;

end ;

/

_dexter@DAVID> desc col_test ;

Name Null? Type

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

ID1 NUMBER

ID2 NUMBER

ID3 NUMBER

ID4 NUMBER

ID5 NUMBER

....

ID99 NUMBER

ID100 NUMBER

复制代码

初始化数据100w

declare

v_sql varchar2(4000) ;

begin

v_sql := 'insert into col_test select ' ;

for i in 1 .. 100 loop

v_sql := v_sql || ' 1 ,' ;

end loop ;

v_sql := substr(v_sql,1,length(v_sql)-1) || ' from dual connect by level <= 1000000 ' ;

execute immediate v_sql ;

commit ;

end ;

/

复制代码

测试耗时

declare

n number ;

begin_t pls_integer ;

end_t pls_integer ;

v_Sql varchar2(4000) ;

begin

for i in 1 .. 101 loop

begin_t := dbms_utility.get_time ;

if i = 1 then

v_sql := 'select count(*) from col_test' ;

else

v_sql := 'select count(id'||(i-1)||') from col_test' ;

end if ;

execute immediate v_sql into n ;

end_t := dbms_utility.get_time ;

dbms_output.put_line('Col'||(i-1)||' : '||(end_t-begin_t)*10);

end loop ;

end ;

/

--下面的单位为毫秒

_dexter@DAVID> /

Col0 : 150

Col1 : 140

Col2 : 110

Col3 : 100

Col4 : 100

Col5 : 110

Col6 : 110

Col7 : 110

Col8 : 110

Col9 : 120

Col10 : 130

Col11 : 120

Col12 : 110

Col13 : 120

Col14 : 120

Col15 : 120

Col16 : 130

Col17 : 120

Col18 : 160

Col19 : 130

Col20 : 140

Col21 : 130

Col22 : 140

Col23 : 140

Col24 : 140

Col25 : 130

Col26 : 160

Col27 : 150

Col28 : 140

Col29 : 150

Col30 : 150

Col31 : 160

Col32 : 180

Col33 : 160

Col34 : 160

Col35 : 160

Col36 : 160

Col37 : 170

Col38 : 180

Col39 : 170

Col40 : 170

Col41 : 180

Col42 : 180

Col43 : 170

Col44 : 210

Col45 : 190

Col46 : 180

Col47 : 180

Col48 : 190

Col49 : 210

Col50 : 190

Col51 : 190

Col52 : 200

Col53 : 200

Col54 : 230

Col55 : 200

Col56 : 210

Col57 : 200

Col58 : 210

Col59 : 220

Col60 : 210

Col61 : 210

Col62 : 220

Col63 : 240

Col64 : 220

Col65 : 220

Col66 : 220

Col67 : 230

Col68 : 270

Col69 : 220

Col70 : 230

Col71 : 230

Col72 : 270

Col73 : 230

Col74 : 240

Col75 : 240

Col76 : 250

Col77 : 240

Col78 : 260

Col79 : 260

Col80 : 280

Col81 : 250

Col82 : 250

Col83 : 250

Col84 : 250

Col85 : 260

Col86 : 260

Col87 : 260

Col88 : 290

Col89 : 260

Col90 : 270

Col91 : 280

Col92 : 280

Col93 : 280

Col94 : 270

Col95 : 310

Col96 : 270

Col97 : 310

Col98 : 290

Col99 : 290

Col100 : 280

PL/SQL procedure successfully completed.

复制代码

可以看到耗时是线性增加的,所以在做表设计的时候,应该注意建表时的列的排列顺序,将常用的列放在靠前的位置

null值测试

关于null值的存储,可以查看我的另外一篇帖子http://www.itpub.net/thread-1718328-1-1.html

因为基于它的存储原理,一个rowpiece中,如果列后面的值都为空,则不再占用存储空间,并且 rowheader cc(Column Count) 也不会将其记录到上面,也就是说如果你的列个数为5,但是这一行的rowpiece中第二列不为空,第三、四、五列为空cc会记为2

但是如果第三、四列都为空,但是第五列不为空,则cc=5 ,第三列第四列使用都使用ff表示,占用了2个字节的存储空间

null
值在表中的位置除了影响存储空间,还会影响查询,因为在扫描数据的时候可以根据cc的个数来确定是否需要扫描column data的数据感兴趣的可以做一下测试

100
列,第50列为有值,其他都为null

下面是测试内容:

建立测试表null_test

declare

v_sql varchar2(4000) ;

begin

v_sql := 'create table null_test (' ;

for i in 1 .. 100 loop

v_sql := v_sql || 'id'||i||' number ,' ;

end loop ;

v_sql := substr(v_sql,1,length(v_sql)-1) || ') ' ;

execute immediate v_sql ;

end ;

/

复制代码

初始化数据100w

insert into null_test(id50) select 100 from dual connect by level <= 1000000 ; commit ;

复制代码

测试

declare

n number ;

begin_t pls_integer ;

end_t pls_integer ;

v_Sql varchar2(4000) ;

begin

for i in 1 .. 101 loop

begin_t := dbms_utility.get_time ;

if i = 1 then

v_sql := 'select count(*) from null_test' ;

else

v_sql := 'select count(id'||(i-1)||') from null_test' ;

end if ;

execute immediate v_sql into n ;

end_t := dbms_utility.get_time ;

dbms_output.put_line('Col'||(i-1)||' : '||(end_t-begin_t)*10);

end loop ;

end ;

/

--测试结果

dexter@FAKE> /

Col0 : 60

Col1 : 80

Col2 : 80

Col3 : 90

Col4 : 90

Col5 : 100

Col6 : 100

Col7 : 100

Col8 : 120

Col9 : 110

Col10 : 120

Col11 : 130

Col12 : 130

Col13 : 140

Col14 : 150

Col15 : 160

Col16 : 160

Col17 : 160

Col18 : 170

Col19 : 170

Col20 : 170

Col21 : 180

Col22 : 180

Col23 : 180

Col24 : 190

Col25 : 190

Col26 : 200

Col27 : 200

Col28 : 200

Col29 : 200

Col30 : 200

Col31 : 210

Col32 : 210

Col33 : 220

Col34 : 220

Col35 : 230

Col36 : 220

Col37 : 220

Col38 : 230

Col39 : 240

Col40 : 240

Col41 : 230

Col42 : 240

Col43 : 250

Col44 : 250

Col45 : 250

Col46 : 260

Col47 : 260

Col48 : 270

Col49 : 260

Col50 : 270

Col51 : 70

Col52 : 80

Col53 : 70

Col54 : 70

Col55 : 80

Col56 : 70

Col57 : 70

Col58 : 80

Col59 : 70

Col60 : 70

Col61 : 70

Col62 : 70

Col63 : 80

Col64 : 80

Col65 : 70

Col66 : 70

Col67 : 70

Col68 : 80

Col69 : 70

Col70 : 70

Col71 : 70

Col72 : 80

Col73 : 70

Col74 : 70

Col75 : 70

Col76 : 80

Col77 : 70

Col78 : 70

Col79 : 70

Col80 : 80

Col81 : 70

Col82 : 70

Col83 : 80

Col84 : 70

Col85 : 70

Col86 : 70

Col87 : 80

Col88 : 70

Col89 : 70

Col90 : 80

Col91 : 70

Col92 : 70

Col93 : 70

Col94 : 80

Col95 : 70

Col96 : 70

Col97 : 70

Col98 : 70

Col99 : 70

Col100 : 80

PL/SQL procedure successfully completed.

复制代码

可以看到前50列的查询耗时是线性增加,但是从第51列开始到最后都维持到一个稳定的值线性增加是因为Oracle 数据库会不断扫描row header 以及 column data 计算要查找的列的位移值而从第51列开始,则可以直接扫描row header CC 个数即可拿到要查找的列的值(null),所以节省了大量扫描column data部分的时间,也证实了上面提出的观点所以设计表的时候,尽量把会存储null值的列放在表的末尾

为了加深印象,我们使用bbed抽出一条数据来看一下它在block中到底是怎样存储的,加深一下印象

dexter@FAKE> select dbms_rowid.rowid_relative_fno(t.rowid) as "FNO#",

2 dbms_rowid.rowid_block_number(t.rowid) as "BLK#",

3 dbms_rowid.rowid_row_number(t.rowid) as "ROW#"

4 from dexter.null_test t

5 where rownum<2 ;

FNO# BLK# ROW#

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

6 284 0

BBED> set dba 6,284

DBA 0x0180011c (25166108 6,284)

BBED> p *kdbr[0]

rowdata[4675]

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

ub1 rowdata[4675] @5878 0x2c

BBED> set offset 5878

OFFSET 5878

BBED> dump /v offset 5878 count 58

File: /u01/apps/oracle/oradata/fake/gg_trans201.dbf (6)

Block: 284 Offsets: 5878 to 5935 Dba:0x0180011c

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

2c0032ff ffffffff ffffffff ffffffff l ,.2.............

ffffffff ffffffff ffffffff ffffffff l ................

ffffffff ffffffff ffffffff ffffffff l ................

ffffffff 02c2022c 0032 l .....Â.,.2

<16 bytes per line>

复制代码

大家一定对count 58 产生疑问,请继续看下去

row header 2c0032

它包括:

2c=flag=00101100=--H-FL--=header+first+last

00=lb itl slot 0x00

32=cc column count = 16x3+2=50

因为这是一个普通的堆积表,所以后面就是rowpiececolumn data了,包括前4949个字节的ff和第50列的列长度02和列值c202

dexter@FAKE> select dump(100,16) from dual ;

DUMP(100,16)

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

Typ=2 Len=2: c2,2

复制代码

so 3+49+1+2=55

后面3个字节是下一行的row header : 2c 0032

LB

ITL

CC

COL#1 value

COL#2-49 value

COL#50 length

COL#50 value

COL#51 value

COL#52-100 value

2C

00

32

FF

FF

02

C202

FF

FF

附录:

rowpiece构成

可以参考官方文档中的说明

图12-7



row piece header包括columns个数,other row piece的位置(当出现row chain或者列个数大于253的时候),cluster key (cluster table)

通常一个row header 至少包含3个字节(状态、itl槽位、column个数)

cluster table的存储情况会在其他章节中介绍

关于row chain的存储,可以我的另外一篇帖子,超过253列的存储
http://www.itpub.net/thread-1719026-1-1.html
LB说明

KCHDFLPN

K=Cluster key (用来表示cluster key)

C=Cluster table member (当表中拥有cluster时使用此标识)

H=Head of row piece (含有Rowpiece header)

D=Delete row (表示已经删除)

F=First data piece (Rowpiece 的第一个piece)

L=Last data piece (Rowpiece 的最后一个piece,Rowchain或者列个数大于253时候会出现)

P=First column continues from previous piece (没有重现过)

N=Last column continues in next piece (没有重现过)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: