阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Oracle Index Clustering Factor(集群因子)

197次阅读
没有评论

共计 11071 个字符,预计需要花费 28 分钟才能阅读完成。

一、本文说明:

    今天在做测试的时候发现字段上有索引,但是执行计划就是不走索引,经过在网上查找才发现原来是索引的集群因子过高导致的。

二、官网说明

    The index clustering factor measures row order in relation to an indexed value suches employee last name.The more order that exists in rowstorage for this value,the lower the clustering factor.

    —-row 存储的越有序,clustering factor 的值越低。

    The clustering factor is useful as a rough measure of the number of I/Os required to read an entire table by means of an index:

    (1)、If the clustering factor is high,then Oracle Database performs a relatively high number of I/Os during a large index range scan.The index entriespoint to random table blocks,so the database may have to read and reread the same blocks over and over again to retrieve the data pointed to by the index.

     —- 当 clustering factor 很高时,说明 index entry (rowid) 是随机指向一些 block 的,在一个大的 index range scan 时,这样为了读取这些 rowid 指向的 block,就需要一次又一次重复的去读这些 block。

    (2)、If the clustering factor is low,then Oracle Database performs a relatively low number of I/Os during a large index range scan.The index keys in arange tend to point to the same data blcok,so the database does not have to read and reread the same blocks over and over.

      —- 当 clustering factor 值低时,说明 index keys (rowid) 是指向的记录是存储在相同的 block 里,这样去读 row 时,只需要在同一个 block 里读取就可以了,这样减少重复读取 blocks 的次数。

      The clustering factor is relevant for index scans because it can show:

          (1)、Whether the database will use an index for large range scans;

          (2)、The degree of table organization in relation to the index key;

          (3)、Whether you should consider using an index-organized table,partitioning,or table cluster if rows must be ordered by the index key.

三、Index Clustering Factor 说明

    简单的说,Index Clustering Factor 是通过一个索引扫描一张表,需要访问的表的数据块的数量,即对 I / O 的影响,也代表索引键存储位置是否有序。

    (1)、如果越有序,即相邻的键值存储在相同的 block,那么这时候 Clustering Factor 的值就越低;

    (2)、如果不是很有序,即键值是随机的存储在 block 上,这样在读取键值时,可能就需要一次又一次的去访问相同的 block,从而增加了 I /O。

    Clustering Factor 的计算方式如下:

    (1)、扫描一个索引(large index range scan);

    (2)、比较某行的 rowid 和前一行的 rowid,如果这两个 rowid 不属于同一个数据块,那么 cluster factor 增加 1;

    (3)、整个索引扫描完毕后,就得到了该索引的 clustering factor。

            如果 clustering factor 接近于表存储的块数,说明这张表是按照索引字段的顺序存储的。

            如果 clustering factor 接近于行的数量,那说明这张表不是按索引字段顺序存储的。

            在计算索引访问成本的时候,这个值十分有用。Clustering Factor 乘以选择性参数 (selectivity) 就是访问索引的开销。

            如果这个统计数据不能真实反映出索引的真实情况,那么可能会造成优化器错误的选择执行计划。另外如果某张表上的大多数访问是按照某个索引做索引扫描,那么将该表的数据按照索引字段的顺序重新组织,可以提高该表的访问性能。

四、测试

  4.1、产生问题:

—- 查看一下数据库的版本 —-
SQL> select * from v$version where rownum=1;

BANNER
——————————————————————————–
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 – Production

  —- 创建一张测试表 jack—-
SQL> create table jack as select * from dba_objects where 1=2;

Table created.

  —- 将数据无序的插入 jack 表中 —-
SQL> begin
  2      for i in 1..10 loop
  3        insert /*+ append */ into jack select * from dba_objects order by i;
  4      commit;
  5    end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

SQL> select count(*) from jack;

  COUNT(*)
———-
    725460

  —- 查看一下表的大小 —–
SQL> set wrap off
SQL> col owner for a10;
SQL> col segment_name for a15;
SQL> select segment_name,blocks,extents,bytes/1024/1024||’M’ “size” from user_segments where segment_name=’JACK’;

SEGMENT_NAME    BLOCKS    EXTENTS  size
————- ———- ———- ——–
JACK            11264      82      88M

  —- 在 object_id 上创建索引 —-
SQL> create index jack_ind on jack(object_id);

Index created.

  —- 查看一下索引的大小 —-
SQL> select segment_name,segment_type,blocks,extents,bytes/1024/1024||’M’ “size” from user_segments where segment_name=’JACK_IND’;

SEGMENT_NAME    SEGMENT_TYPE      BLOCKS    EXTENTS    size
———— —————— ———- ———- ———
JACK_IND          INDEX          1664        28        13M
  —- 在没有收集相关的统计信息之前,查看一下 index clustering factor—-
SQL> select index_name,clustering_factor,num_rows from user_indexes where index_name=’JACK_IND’;

INDEX_NAME      CLUSTERING_FACTOR  NUM_ROWS
————— —————– ———-
JACK_IND              725460        725460

  —- 简单的收集一下统计信息 —-
SQL> exec dbms_stats.gather_table_stats(user,’jack’,cascade=>true);

PL/SQL procedure successfully completed.

  —- 再次查看 index clustering factor—-
SQL> select index_name,clustering_factor,num_rows from user_indexes where index_name=’JACK_IND’;

INDEX_NAME    CLUSTERING_FACTOR  NUM_ROWS
————– —————– ———-
JACK_IND              725460      725460      —- 显然统计信息收集前和后,clustering factor 值不变,说在创建索引的时候,会收集表中的数据真正的行数。并且这里的 clustering factor 等 num_rows,也说明表的 clustering factor 是无序的。

  —- 查看一个确定值,然后查看执行计划 —-
SQL> explain plan for select * from jack where object_id=1501;

Explained.

SQL> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
——————————————————————————–
Plan hash value: 2860868395

——————————————————————————–
| Id  | Operation            | Name    | Rows  | Bytes | Cost (%CPU)| Ti
——————————————————————————–
|  0 | SELECT STATEMENT        |          |    10 |  970 |    13    (0)| 00
|  1 |  TABLE ACCESS BY INDEX ROWID| JACK    |    10 |  970 |    13    (0)| 00
|*  2 |  INDEX RANGE SCAN        | JACK_IND |    10 |      |    3    (0)| 00
——————————————————————————–

Predicate Information (identified by operation id):

PLAN_TABLE_OUTPUT
——————————————————————————–

  2 – access(“OBJECT_ID”=1501)

14 rows selected.                —- 在这里走了索引,cost 为 13.

SQL> alter system flush buffer_cache;

System altered.

SQL> set autotrace traceonly;
  —- 查询一个范围的执行计划 —-
SQL> select * from jack where object_id>1000 and object_id<2000;

9880 rows selected.

Execution Plan
———————————————————-
Plan hash value: 949574992

————————————————————————–
| Id  | Operation      | Name | Rows  | Bytes | Cost (%CPU)| Time    |
————————————————————————–
|  0 | SELECT STATEMENT  |    |  9657 |  914K|  1824  (1)| 00:00:22 |
|*  1 |  TABLE ACCESS FULL| JACK |  9657 |  914K|  1824  (1)| 00:00:22 |
————————————————————————–

Predicate Information (identified by operation id):
—————————————————

  1 – filter(“OBJECT_ID”<2000 AND “OBJECT_ID”>1000)

Statistics
———————————————————-
      0  recursive calls
      0  db block gets
    10993 consistent gets
    10340 physical reads
      0  redo size
  471945 bytes sent via SQL*Net to client
    7657  bytes received via SQL*Net from client
    660  SQL*Net roundtrips to/from client
      0  sorts (memory)
      0  sorts (disk)
    9880  rows processed      —- 注意,object_id 上是有索引的,但是这里并没有使用索引,而是使用了全表扫描。

SQL> alter system flush buffer_cache;

System altered.

 —- 强制走索引,查看执行计划 —-
SQL> select /*+ index(jack jack_ind) */ * from jack where object_id>1000 and object_id<2000;

9880 rows selected.

Execution Plan
———————————————————-
Plan hash value: 2860868395

—————————————————————————————-
| Id  | Operation            | Name    | Rows  | Bytes | Cost (%CPU)| Time    |
—————————————————————————————-
|  0 | SELECT STATEMENT        |          |  9657 |  914K|  9683    (1)| 00:01:57 |
|  1 |  TABLE ACCESS BY INDEX ROWID| JACK    |  9657 |  914K|  9683    (1)| 00:01:57 |
|*  2 |  INDEX RANGE SCAN        | JACK_IND |  9657 |      |    24    (0)| 00:00:01 |
—————————————————————————————-

Predicate Information (identified by operation id):
—————————————————

  2 – access(“OBJECT_ID”>1000 AND “OBJECT_ID”<2000)

Statistics
———————————————————-
      0    recursive calls
      0    db block gets
    10561  consistent gets
    164    physical reads
      0    redo size
  988947  bytes sent via SQL*Net to client
    7657  bytes received via SQL*Net from client
    660    SQL*Net roundtrips to/from client
      0    sorts (memory)
      0    sorts (disk)
    9880  rows processed   
—- 强制走索引之后,使用了 index range scan, 但是 cost 变成了 9683,而全表扫描时是 1824.
—- 还有比较一下两次查询中物理读的情况:全表扫描的物理读明显比索引的要高很多,但是 Oracle 却没有使用索引。
—- 因此 Oracle 认为走索引的 Cost 比走全表扫描大,而是大 N 倍,CBO 是基于 Cost 来决定执行计划的。
—- 由此得出,对于索引的 Cost,Oracle 是根据 clustering factor 参数来计算的,而该实验中的 clustering factor 参数是很高的,数据存储无序。这就造成了 Oracle 认为走索引的 cost 比全表扫描的大。

 4.2、解决问题:

—- 通过上面的分析,可以看出,要降低 clustering factor 才能解决问题,而要解决 clustering factor,就需要重新对表的存储位置进行排序。—-
  —- 重建 jakc 表 —-
SQL> create table echo as select * from jack where 1=0;

Table created.

SQL> insert /*+ append */ into echo select * from jack order by object_id;

725460 rows created.

SQL> commit;

Commit complete.

SQL> truncate table jack;

Table truncated.

SQL> insert /*+ append */ into jack select * from echo;

725460 rows created.

SQL> commit;

Commit complete.

  —- 查看表和索引的信息 —-
SQL> select segment_name,blocks,extents,bytes/1024/1024||’M’ “size” from user_segments where segment_name=’JACK’;

SEGMENT_NAME    BLOCKS    EXTENTS    size
————- ———- ———- ———–
JACK            11264      82        88M

SQL> select segment_name,segment_type,blocks,extents,bytes/1024/1024||’M’ “size” from user_segments where segment_name=’JACK_IND’;

SEGMENT_NAME    SEGMENT_TYPE      BLOCKS    EXTENTS    size
———— —————— ———- ———- ————-
JACK_IND            INDEX          1536          27    12M

SQL> select index_name,clustering_factor,num_rows from user_indexes where index_name=’JACK_IND’;

INDEX_NAME    CLUSTERING_FACTOR NUM_ROWS
————- —————– ———-
JACK_IND            725460      725460

  —- 对索引进行 rebuild—-
SQL> alter index jack_ind rebuild;

Index altered.

  —- 查看 cluster factor—-
SQL> select index_name,clustering_factor,num_rows from user_indexes where index_name=’JACK_IND’;

INDEX_NAME      CLUSTERING_FACTOR  NUM_ROWS
————— —————– ———-
JACK_IND              10327      725460    —— 注意这里的 Factor,已经变成 10327,我们收集一下表的统计信息,然后与表的 block 进行一次比较。

SQL> exec dbms_stats.gather_table_stats(user,’jack’,cascade=>true);

PL/SQL procedure successfully completed.

SQL> select blocks from dba_tables where table_name=’JACK’;

    BLOCKS
———-
    10474  —- 表 jack 实际使用的 block 是 10474,clustering factor 是 10327 基本还是比较接近了,这也说明相邻的 row 是存储在相同的 block 里。

SQL> select index_name,clustering_factor,num_rows from user_indexes where index_name=’JACK_IND’;

INDEX_NAME              CLUSTERING_FACTOR  NUM_ROWS
—————————— —————– ———-
JACK_IND                  10327    725460

SQL> alter system flush buffer_cache;

System altered.

SQL> set autotrace traceonly;
  —- 再次查看之前 sql 的执行计划 —-
SQL> select * from jack where object_id>1000 and object_id<2000;

9880 rows selected.

Execution Plan
———————————————————-
Plan hash value: 2860868395

—————————————————————————————-
| Id  | Operation            | Name    | Rows  | Bytes | Cost (%CPU)| Time    |
—————————————————————————————-
|  0 | SELECT STATEMENT        |          |  9657 |  914K|  162    (0)| 00:00:02 |
|  1 |  TABLE ACCESS BY INDEX ROWID| JACK    |  9657 |  914K|  162    (0)| 00:00:02 |
|*  2 |  INDEX RANGE SCAN        | JACK_IND |  9657 |      |    24    (0)| 00:00:01 |
—————————————————————————————-

Predicate Information (identified by operation id):
—————————————————

  2 – access(“OBJECT_ID”>1000 AND “OBJECT_ID”<2000)

Statistics
———————————————————-
      1  recursive calls
      0  db block gets
    1457  consistent gets
    151  physical reads
      0  redo size
  988947  bytes sent via SQL*Net to client
    7657  bytes received via SQL*Net from client
    660  SQL*Net roundtrips to/from client
      0  sorts (memory)
      0  sorts (disk)
    9880  rows processed
  —- 注意这里的 cost 已经降到了 162,性能提升还是非常明显。

 五、小结

    通过以上说明和测试,可以看到 clustering factor 也是索引健康的一个重要判断的标准。其值越低越好。它会影响 CBO 选择正确的执行计划。但是注意一点,clustering factor 总是趋势与不断恶化的。

更多 Oracle 相关信息见Oracle 专题页面 http://www.linuxidc.com/topicnews.aspx?tid=12

本文永久更新链接地址:http://www.linuxidc.com/Linux/2018-01/150141.htm

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-22发表,共计11071字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中