聊聊Clustering Factor的两个细节问题

Clustering Factor聚簇因子是Oracle索引的一种重要参数指标。对CBO而言,clustering factor的取值能够影响到索引路径访问成本值,最后影响到SQL语句的执行计划。

本篇对clustering factor的两个细节问题进行简单讨论,供有兴趣的朋友参考。

1Clustering Factor的计数

通常,我们认为clustering factor是一个影响CBO进行索引路径成本计算的统计量。Clustering Factor是归属在索引对象的统计量中,在收集统计量时合并的计入索引的数据字典中。

Clustering Factor在索引路径成本公式中扮演着重要的作用。普遍认可的索引路径成本公式为:

 

 

Cost=1+

     blevel+

     ceil(effective index selectivity * leaf_blocks)+

     ceil(effective table selectivity * clustering_factor)

 

 

 

在公式中,我们看到clustering_factor在成本公式中的作用。如果该值增加,会引起索引路径成本计算值的增加。反之,cost值就会减少。Cost计算值比较会最后影响CBO的决策。CBO的生命力其实就在于选择路径多、优胜劣汰。在笔者之前的一篇文章里面,已经讨论过这个现象。http://space.itpub.net/17203031/viewspace-680936

 

 

SQL> select index_name, clustering_factor, NUM_ROWS from dba_indexes where rownum<5;

INDEX_NAME CLUSTERING_FACTOR   NUM_ROWS

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

I_COL2                  3473     161848

I_USER#                    9        120

I_OBJ#                  3809      17633

I_CON2                   181      25934

 

 

 

从含义上看,clustering factor反映的是数据块顺序程度和索引叶子节点的相合程度。索引叶子节点是有序的,其实就是反映数据行在数据块里面的离散程度,相同或者相邻的索引值行是不是在相同的数据块上。

例如说,我们从索引树叶子节点上面获取到10个数据行rowid,根据他们进行回表操作。而在堆表(heap table)的环境下,这10行数据保存位置离散程度我们是不知道的。可能这10行是在10个数据块,也可能是1个数据块。但是,落实到估算成本和实际成本上,就有很大差距。

Oracle中,Index路径回表的时候是选择单块读操作,也就是一次只读一个数据块。这就体现出成本I/O的差别了,读1个块和读10个块肯定是有差别。就是这样的一出一入,计算成本和实际成本就上去了,索引路径也许就不如全表扫描了。

那么,clustering factor的计量标准是什么呢?我们普遍关注的就是factornum_rows的对比关系。行业中的一条传言是“优化factor近似要等于num_rows的一半”。Clustering factor是一个介于0num_rows之间的取值。

Clustering factor究竟是如何计数的呢?过程如下:Oracle会按照索引的叶子节点顺序,从左侧扫描到右侧,这样就可以获得顺序化的索引列rowid列表。之后,按照这样的顺序去依次定位数据行位置。在定位数据块的过程中,如果一次读取行所在数据块和上一次读的行不在一个数据块上,也就是发生了“跳块”动作,统计计数量就加1

下面,我们通过一个实验来模拟这个运算过程。首先我们选择11.2作为实验环境,创建实验数据表。

 

 

SQL> select * from v$version;

BANNER

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

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

PL/SQL Release 11.2.0.1.0 - Production

CORE 11.2.0.1.0 Production

TNS for 32-bit Windows: Version 11.2.0.1.0 - Production

NLSRTL Version 11.2.0.1.0 - Production

SQL> create table t (id number, owner varchar2(1000), object_name varchar2(1000)) pctfree 99 pctused 1;

Table created

SQL> insert into t select object_id, owner, object_name from dba_objects where rownum<10;

9 rows inserted

SQL> commit;

Commit complete

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

 

 

注意,我们使用pctfreepctused参数来控制数据行在数据块中的分布。我们实现了将9行数据分散在多个数据块上。

 

 

SQL> select blocks from dba_tables where owner='SYS' and table_name='T';

    BLOCKS

----------

         5

SQL> select extents, blocks from dba_segments where owner='SYS' and segment_name='T';

   EXTENTS     BLOCKS

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

         1          8

SQL> create index idx_t_id on t(id);

Index created

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

 

 

数据表T上面创建了索引idx_t_id,数据表本身分配一个分区extent,数据分布在水位线(HWM)下的5个数据块中。

此时,我们查看索引数据字典中的clustering factor取值。

 

 

SQL> select blevel, num_rows, clustering_factor from dba_indexes where owner='SYS' and index_name='IDX_T_ID';

    BLEVEL   NUM_ROWS CLUSTERING_FACTOR

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

         0          9                 9

 

 

 

此时,clustering factor取值和num_rows相同。那么,这个值是怎么计数出来的呢?我们可以使用rowid定位有序object_id情况下,数据块的变化情况。

 

 

SQL> select dbms_rowid.rowid_block_number(rowid) block_num, dbms_rowid.rowid_row_number(rowid) row_num, id from t order by id;

 BLOCK_NUM    ROW_NUM         ID

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

     89347          1          3

     89346          1         15

     89345          0         20

     89348          0         25

     89346          0         28

     89347          0         29

     89348          1         41

     89345          1         46

     89349          0         54

9 rows selected

 

 

 

可以发现,每次id的变化,都会发生块的跳跃,所以扫描9行数据发生的块跳跃一共是9次。

Oracle在收集统计量时候是怎么做的呢?一些观点认为如果每个索引都进行处理一遍,消耗时间和资源过多。笔者猜想可能是采用抽样或者计算索引叶子节点rowid的信息变化类似的方法。

 

2Rebuild索引与Clustering Factor

 

Clustering Factor优化是很多运维人员的目标。同时,一些运维人员认为rebuild索引是下降优化clustering factor的好方法。这样的想法其实是错误的。

 

Clustering Factor虽然是索引的统计属性,但是反映的却是数据行在数据段里面的相对位置。Rebuild索引不会影响有效叶子节点之间的相对顺序关系,索引在rebuild前后,都会维持相对的顺序一致性。而数据表中数据行的顺序关系,完全不能通过rebuild工作完成。所以,rebuild不会优化clustering factor

下面通过实验来证明。

 

 

SQL> create table t as select * from dba_objects where 1=0;

Table created

--先创建索引

SQL> create index idx_t_id on t(object_id);

Index created

--后插入数据

SQL> insert into t select * from dba_objects;

84332 rows inserted

SQL> commit;

Commit complete

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

 

 

注意,我们这里面使用的是“先创建索引,后插入数据”的过程。根据Oracle索引叶子节点分裂算法,此时依据的“5/5”分裂原则进行分裂。

5/5”分裂原则下,索引叶子节点相对比较松散。我们检查Clustering Factor取值情况。

 

 

 

SQL> select blevel, num_rows, clustering_factor, leaf_blocks from dba_indexes where owner='SYS' and index_name='IDX_T_ID';

    BLEVEL   NUM_ROWS CLUSTERING_FACTOR LEAF_BLOCKS

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

         1      84330              1588         298

 

 

 

容纳84330行数据的索引列值,索引idx_t_id分配了叶子节点个数为298个,clustering factor取值为1588。下面我们rebuild一下。

 

 

SQL> alter index idx_t_id rebuild;

Index altered

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

SQL> select extents, blocks from dba_segments where owner='SYS' and segment_name='T';

   EXTENTS     BLOCKS

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

        25       1280

SQL> select blevel, num_rows, clustering_factor, leaf_blocks from dba_indexes where owner='SYS' and index_name='IDX_T_ID';

    BLEVEL   NUM_ROWS CLUSTERING_FACTOR LEAF_BLOCKS

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

         1      84330              1588         187

 

 

 

在索引进行rebuild的时候,Oracle索引列是全部被取出排序之后,依据顺序生成索引。这样的方式下,分裂算法是“9/1”算法。叶子节点相对紧密,叶子块个数和索引段相对小。

我们发现,rebuild之后,lead_blocks叶子节点个数从298下降到187。但是clustering factor取值却没有变化,说明rebuild不会优化其恶化情况。

 

 

3、结论

 

 

clustering factor的研究,有助于我们对于CBO工作原理和优化原则的理解。更有意义的是可以进一步做好优化策略和运维策略。

请使用浏览器的分享功能分享到微信等