Oracle 聚簇因子(Clustering factor)及一次性能优化诊断
聚簇因子是 Oracle 统计信息中在CBO优化器模式下用于计算cost的参数之一,决定了当前的SQL语句是否走索引,还是全表扫描以及是否作为嵌套连接外部表等。如此这般,那到底什么是聚簇因子,那些情况下会影响到聚簇因子,以及如何提高聚簇因子?本文将对此展开描述。
1、堆表的存储方式
Oralce 数据库系统中最普通,最为常用的即为堆表。
堆表的数据存储方式为无序存储,也就是任意的DML操作都可能使得当前数据块存在可用的空闲空间。
处于节省空间的考虑,块上的可用空闲空间会被新插入的行填充,而不是按顺序填充到最后被使用的块上。
上述的操作方式导致了数据的无序性的产生。
当创建索引时,会根据指定的列按顺序来填充到索引块,缺省的情况下为升序。
新建或重建索引时,索引列上的顺序是有序的,而表上的顺序是无序的,也就是存在了差异,即表现为聚簇因子。
2、什么是聚簇因子(clustering factor/CF)
聚簇因子是基于表上索引列上的一个值,每一个索引都有一个聚簇因子。
用于描述索引块上与表块上存储数据在顺序上的相似程度,也就说表上的数据行的存储顺序与索引列上顺序是否一致。
在全索引扫描中,CF的值基本上等同于物理I/O或块访问数,如果相同的块被连续读,则Oracle认为只需要1次物理I/O。
好的CF值接近于表上的块数,而差的CF值则接近于表上的行数。
聚簇因子在索引创建时就会通过表上存存在的行以及索引块计算获得。
3、Oracle 如何计算聚簇因子
执行或预估一次全索引扫描。
检查索引块上每一个rowid的值,查看是否前一个rowid的值与后一个指向了相同的数据块,如果指向了不相同的数据块则CF的值增加1。
当索引块上的每一个rowid被检查完毕,即得到最终的CF值。
4、聚簇因子图示
a、良好的索引与聚簇因子的情形
b、良好的索引、差的聚簇因子的情形
c、差的索引、差的聚簇因子的情形
一次诊断:
业务人员反映一个查询非常慢:
--------------------------------------------------------------------------------
select * from ab44 where aae002=201006;
--------------------------------------------------------------------------------
查看执行计划,是全表扫描
SQL> explain plan for select * from ab44 where aae002=201006;
已解释。
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 781340439
--------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10554 | 865K| 8777 (3)| 00:01:46 |
|* 1 | TABLE ACCESS FULL| AB44 | 10554 | 865K| 8777 (3)| 00:01:46 |
--------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
1 - filter("AAE002"=201006)
已选择13行。
看看查询应该返回多少数据量,还有这个表有多少记录。
SQL> select count(*) from ab44 where aae002='201006';
COUNT(*)
----------
576
SQL> select count(*) from ab44;
COUNT(*)
----------
3310023
SQL> select 576/3310023 from dual;
576/3310023
-----------
.000174017
查询所需返回的行数仅占表的很小比例,如果有索引的话,应该索引扫描才对。
查看表的索引,发现在aae002字段上有一个复合索引,四个字段组成AAE002, AAE003, AAB001, AAE140。既然有索引,为什么没有使用呢?莫非是缺失统计信息。
查看表、索引、直方图的信息都有。而且统计信息相对还是比较新的。
SQL> select num_rows,blocks,avg_row_len from user_tables where table_name='AB44';
NUM_ROWS BLOCKS AVG_ROW_LEN
---------- ---------- -----------
3310017 44538 84
SQL> select distinct_keys,clustering_factor,num_rows from USER_IND_STATISTICS WHERE table_name='AB44' and index_name='PK_AB44';
DISTINCT_KEYS CLUSTERING_FACTOR NUM_ROWS
------------- ----------------- ----------
3309447 3299907 3309447
SQL> SELECT * FROM USER_HISTOGRAMS WHERE table_name='AB44';
略。。。。。。。。。。。。。。。。。。。。。。。。
查询到索引的统计信息的时候,发现索引的聚簇因子非常高,非常接近表的行数。重新分析表,依然如此。
修改聚簇因子后,查看执行计划,已经是索引扫描了。
begin
dbms_stats.set_index_stats(ownname => 'NCSI',indname => 'PK_AB44',clstfct => '7800');
end;
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
-----------------------------------------------------------------------------------------------
-------------
Plan hash value: 1618544176
---------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10554 | 865K| 239 (1)| 00:00:03 |
| 1 | TABLE ACCESS BY INDEX ROWID| AB44 | 10554 | 865K| 239 (1)| 00:00:03 |
|* 2 | INDEX RANGE SCAN | PK_AB44 | 10554 | | 45 (0)| 00:00:01 |
---------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("AAE002"=201006)
已选择14行。
但是到这里并不能说一定是聚簇因子导致的,因为很可能是还有直方图的因素。查询列AAE002上的唯一值个数为420,而表的记录总数是330万,如果没有直方图的话,ORACLE评估返回的行数应该是3300000/420=7857条记录,按照这个记录量来看,返回的行数占表记录总数的0.2%.根据经验,应该也能使用到索引才对。
于是重新收集统计信息,取消直方图。查看执行计划,还是全表扫描。看来直方图在本例中所占影响因素较小,还是聚簇因子过大惹的祸。
暂时通过修改聚簇因子暂时改善了性能问题,晚上的时候,按照索引字段的顺序重新创建了表。
SQL>create table AB44_TEMP as select * from ab44 where 1=0;
SQL>INSERT /*+ append */INTO AB44_TEMP SELECT * FROM AB44 ORDER BY AAE002, AAE003, AAB001, AAE140;
SQL>commit;
SQL>drop table ab44;
SQL>alter table ab44_temp rename to ab44;
重新创建索引,分析表。重建后的聚簇因子只有60197,远远小于之前的 3299907。查看执行计划,也对了。
SQL> explain plan for select * from ab44 where aae002=201006;
已解释。
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
-------------
Plan hash value: 2627288474
---------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10799 | 885K| 249 (1)| 00:00:03 |
| 1 | TABLE ACCESS BY INDEX ROWID| AB44 | 10799 | 885K| 249 (1)| 00:00:03 |
|* 2 | INDEX RANGE SCAN | AB44_TEMP_IND | 10799 | | 50 (0)| 00:00:01 |
---------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("AAE002"=201006)
已选择14行。
而且为了验证本例确实是由于聚簇因子过大占了决定因素。我把重建后的表直方图取消掉,重新查询,每一个AAE002的值都是索引扫描了。而之前聚簇因子较大的无直方图的实验,还是全表扫描。进一步证明了本例聚簇因子的影响占了很大比例。
BEGIN
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME => 'NCSI',
TABNAME => 'AB44',
CASCADE => TRUE,
METHOD_OPT => 'for ALL columns SIZE 1');
END;
SQL>EXPLAIN PLAN FOR SELECT * FROM ab44 WHERE aae002=201002;
SQL>EXPLAIN PLAN FOR SELECT * FROM ab44 WHERE aae002=201006;
SQL>EXPLAIN PLAN FOR SELECT * FROM ab44 WHERE aae002=198701;
SQL>EXPLAIN PLAN FOR SELECT * FROM ab44 WHERE aae002=199101;
SQL>EXPLAIN PLAN FOR SELECT * FROM ab44 WHERE aae002=199804;
SQL> select object_name,operation,options from plan_table where id=2;
OBJECT_NAME OPERATION OPTIONS
-------------------- ------------------------------ --------------------
AB44_TEMP_IND INDEX RANGE SCAN
AB44_TEMP_IND INDEX RANGE SCAN
AB44_TEMP_IND INDEX RANGE SCAN
AB44_TEMP_IND INDEX RANGE SCAN
AB44_TEMP_IND INDEX RANGE SCAN