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
因为这是一个普通的堆积表,所以后面就是rowpiece的column data了,包括前49列49个字节的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
附录:
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 (没有重现过)
大家在做表设计的时候通常对表中列的排列顺序没有过多注意,但是其实越常用的列,它的位置越靠前,则查询速度越快。
因为每个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
因为这是一个普通的堆积表,所以后面就是rowpiece的column data了,包括前49列49个字节的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 (没有重现过)
相关文章推荐
- SQL-Oracle10数据库设计范式
- SQL-Oracle09嵌套表与可变数组
- SQL-Oracle08sql序列和同义词
- SQL-Oracle08sql视图
- SQL-Oracle07sql约束
- SQL-Oracle06表的管理
- SQL-Oracle05数据库更新操作
- SQL-Oracle04事务处理
- SQL-Oracle03-子查询
- SQL语句-Oracle02-多表查询
- SQL语句-Oracle01
- oracle数据库中的一些name的说明
- oracle分区
- oracle 索引
- Oracle 实例恢复详解 MTTR
- 虽然遭遇Oracle的挑战,Spring框架依旧蓬勃发展
- ORACLE物化视图--物化视图日志如何避免系统时间变化带来的影响
- oracle locks query
- linux下快速删除oracle
- centos 6.5_x64上静默模式安装oracle 11.2.0.1