大家在大型数据库生产系统的运维中可能会遇到这样一个问题,一条查询语句,操作的是相同的表和数据,为什么在生产数据库上执行起来就很慢,而在备份数据库反而会很快。这其中一个重要原因就在于索引cluster_factor的不同。
   oracle数据库下,索引在做完统计分析后,会获得很多重要信息,其中之一就是cluster_factor,cluster_factor表示索引数据顺序和表数据顺序的一致性,关于cluster_factor的理论和机制分析见随后作者的文章,oracle高级sql调优之:cluster_factor机制研究。
   cluster_factor的精彩之处就在于,能借此区分看来貌似完全相同的情况:表结构、表数据和索引完全相同,但就是表数据行的存储顺序不同。下面以案例的形式加以分析。
   1. 研究结论
   cluster_factor对oracle执行计划会产生重要影响。这个值越高,说明索引的使用效率将会越差。这个值会存在于一个区间内,区间的最小值为表占用的数据块数,最大值为表拥有的数据行数。
   2. 研究对象
   研究对象为两个数据表testcf和testcf2,两者的数据结构相同,都只有两列:id列,整数型,name列,字符型,80个字符,数据相同,都8万行,占用1024个数据块。不同的在于两个表的数据行的存储顺序不同,testcf表的数据,按照id值从小到大的顺序依次存储,而testcf2表的数据,随机杂乱存储。
   3. 案例实验过程
   3.1 系统配置:
   oracle 11.1.0.6,初始化参数optimizer_index_cost_adj为默认值100
   sql>  select  from v$version 
   banner
   --------------------------------------------------------------------------------
   oracle database 11g enterprise edition release 11.1.0.6.0 - production
   pl/sql release 11.1.0.6.0 - production
   core 11.1.0.6.0 production
   tns for 32-bit windows: version 11.1.0.6.0 - production
   nlsrtl version 11.1.0.6.0 – production
   sql>  select name  type  value from v$parameter p where p.name = ’optimizer_index_cost_adj’ 
   name type value
   ------------------------------ ---------- --------------------
   optimizer_index_cost_adj 3 100  3.2 创建表testcf和testcf2
   设置表tescf控制其行长度和行数使得总共占用约1024个数据块
   表定义:每行至少80个字节,共8万行,pctfree = 0,初始盘区和next盘区都为1m
   3.2.1 创建表testcf,并产生数据
   数据的两列,分别由类序列值和随机函数产生,随机函数直接产生80位长的字符
   sql>  create table testcf
   2 (
   3 id number(32) 
   4 name varchar2(80)
   5 )
   6 tablespace users
   7 pctfree 0
   8 initrans 1
   9 maxtrans 255
   10 storage
   11 (
   12 initial 1m
   13 next 1m
   14 minextents 1
   15 maxextents unlimited
   16 ) 
   表已创建。
   已用时间: 00: 00: 00.06
   sql>  begin
   2 for i in 1..80000 loop
   3 insert into testcf(id  name)
   4 values(i dbms_random.string(’a’ 80)) 
   5 end loop 
   6 commit 
   7 end 
   8 /
   pl/sql 过程已成功完成。
   已用时间: 00: 00: 18.53  3.2.2 创建表testcf2
   该表和表testcf结构相同,且数据相同。将testcf表的所有数据灌入testcf2以获得相应信息。
   重要:灌入时,让数据随机的进入testcf2,由dbms_random控制员vぷ愎坏乃婊浴?br>   sql>  create table testcf2
   2 (
   3 id number(32) 
   4 name varchar2(80)
   5 )
   6 tablespace users
   7 pctfree 0
   8 initrans 1
   9 maxtrans 255
   10 storage
   11 (
   12 initial 1m
   13 next 1m
   14 minextents 1
   15 maxextents unlimited
   16 ) 
   表已创建。
   已用时间: 00: 00: 00.04
   sql> 
   sql>  insert into testcf2 nologging
   2 select  from testcf order by dbms_random.random 
   已创建80000行。
   已用时间: 00: 00: 01.28
   sql> 
   sql>  commit 
   提交完成。
   已用时间: 00: 00: 00.00  3.3 给两个表都创建pk
   sql>  alter table testcf add constraint pk_testcf primary key(id) 
   表已更改。
   已用时间: 00: 00: 00.71
   sql> 
   sql>  alter table testcf2 add constraint pk_testcf2 primary key(id) 
   表已更改。
   已用时间: 00: 00: 00.37
   表都为1024个数据块,索引都为256个数据块。
   sql>  select t.segment_name  t.segment_type  t.blocks from user_segments t where t.segment_name like ’testcf’ 
   segment_name segment_type blocks
   -------------------- ------------------------------------ ----------
   testcf2 table 1024
   pk_testcf index 256
   testcf table 1024
   pk_testcf2 index 256
   已用时间: 00: 00: 00.10
   3.4 查看数据样例
   sql>  select  from testcf where rownum <  3 
   id name
   ---------- --------------------------------------------------------------------------------
   1 xmamnromseweamypdopxkesqzknbqrxloexsahigzirranrtzprtooawwooeimygjtwbuhwcxhplskky
   2 aevffxiltlwjtgnjcotvuovwwgfhzkvxtjjokgrdftkonklzvignzfuxlanfhdimvgxdnfmhhejizhvs
   已用时间: 00: 00: 00.00
   sql>  select  from testcf2 where rownum <  3 
   id name
   ---------- --------------------------------------------------------------------------------
   39312 uwnqugvticiholgfccbnivhouteszhvphwljeeydkukfuywcckikyrkpqsiunrjqyursqejnwmsdteqw
   41453 cosqpzchzgbykhnjbhigbwuybkqucbrctnhbyhyvjdnitsxpxdkwxzsdyiksbujsuiziolellwpvosny
   已用时间: 00: 00: 00.01  3.5分别产生统计数据,然后查看两个表pk索引的cluster_factor
   可以看出两表pk索引的cluster_factor值相差甚远。
   testcf表的值为908,接近于表的数据块数(1024),
   testcf2表的值为79913,接近于表的数据行数(80000),
   这说明,当数据行的存储顺序和索引顺序越接近,cluster_factor越小,越有利于使用索引。
   sql>  begin
   2 dbms_stats.gather_table_stats(ownname =>  user tabname =>  ’testcf’ cascade =>  true) 
   3 end 
   4 /
   pl/sql 过程已成功完成。
   已用时间: 00: 00: 00.93
   sql>  begin
   2 dbms_stats.gather_table_stats(ownname =>  user tabname =>  ’testcf2’ cascade =>  true) 
   3 end 
   4 /
   pl/sql 过程已成功完成。
   已用时间: 00: 00: 00.45
   sql>  select i.index_name  i.clustering_factor from dba_indexes i where i.index_name like ’pk_testcf’ 
   index_name clustering_factor
   ------------------------------ -----------------
   pk_testcf 908
   pk_testcf2 79913
   3.6 测试cluster_factor对执行计划的影响
   执行相同的查询语句,获得一段连续id内的数据行,以检查cluster_factor对执行计划的影响
   从实验可以看出,cluster_factor对执行计划产生了巨大影响,这使得
   a) testcf表的执行计划,走的是index range scan,而test2表走的是table access full
   同事这导致testcf表的总cost为83,testcf2表的总cost为210,是后者的四倍。
   b) testcf表的物理读和一致性读远小少于testcf2。
   sql>  alter system flush buffer_cache 
   系统已更改。
   已用时间: 00: 00: 00.06
   sql>  set autotrace traceonly 
   sql>  select  from testcf where id >  2000 and id <  8000 
   已选择5999行。
   已用时间: 00: 00: 00.65  执行计划
   ----------------------------------------------------------
   plan hash value: 2216396729
   --------------------------------------------------------------------------------------
   | id | operation | name | rows | bytes | cost (cpu)| time
   --------------------------------------------------------------------------------------
   | 0 | select statement | | 6001 | 498k| 83 (0)| 00:00:0
   | 1 | table access by index rowid| testcf | 6001 | 498k| 83 (0)| 00:00:0
   | 2 | index range scan | pk_testcf | 6001 | | 14 (0)| 00:00:0
   --------------------------------------------------------------------------------------
   predicate information (identified by operation id):
   ---------------------------------------------------
   2 - access(" id" > 2000 and " id" < 8000)
   统计信息
   ----------------------------------------------------------
   320 recursive calls
   0 db block gets
   929 consistent gets
   121 physical reads
   0 redo size
   592212 bytes sent via sqlnet to client
   4774 bytes received via sqlnet from client
   401 sqlnet roundtrips to/from client
   5 sorts (memory)
   0 sorts (disk)
   5999 rows processed
   sql>  alter system flush buffer_cache 
   系统已更改。
   已用时间: 00: 00: 00.03
   sql>  set autotrace traceonly 
   sql>  select  from testcf2 where id >  2000 and id <  8000 
   已选择5999行。
   已用时间: 00: 00: 00.59  执行计划
   ----------------------------------------------------------
   plan hash value: 4178501150
   -----------------------------------------------------------------------------
   | id | operation | name | rows | bytes | cost (cpu)| time |
   -----------------------------------------------------------------------------
   | 0 | select statement | | 6001 | 498k| 210 (2)| 00:00:03 |
   | 1 | table access full| testcf2 | 6001 | 498k| 210 (2)| 00:00:03 |
   -----------------------------------------------------------------------------
   predicate information (identified by operation id):
   ---------------------------------------------------
   1 - filter(" id" < 8000 and " id" > 2000)
   统计信息
   ----------------------------------------------------------
   312 recursive calls
   0 db block gets
   1383 consistent gets
   944 physical reads
   0 redo size
   568273 bytes sent via sqlnet to client
   4774 bytes received via sqlnet from client
   401 sqlnet roundtrips to/from client
   5 sorts (memory)
   0 sorts (disk)
   5999 rows processed
   sql>