Oracle索引聚簇因子的含义及重要性

简介:



索引统计信息中需要我们最为重点关注的是CLUSTERING_FACTOR(聚簇因子)

Oracle数据库中,聚簇因子是指按照索引键值排序的索引行和存储于对应表中数据行的存储顺序和相似度。Oracle是按照如下的算法来计算聚簇因子的值:

  1. 聚簇因子的初始值为1。

  2. Oracle首先定位到目标索引处于最左边的叶子块。

  3. 从最左边的叶子块的第一个索引键值所在的索引行开始顺序扫描,在顺序扫描的过程中,Oracle会比对当前索引行的rowid和它之前的那个索引行(它们是相邻的关系)的rowid,如果这两个rowid并不是指向同一个表块,那么Oracle就将聚簇因子的当前值递增1;如果这两个rowid是指向同一个表块,Oracle就不改变聚簇因子的当前值。注意,这里Oracle在比对rowid时不需要回表去访问相应的表块。

  4. 上述比对rowid的过程会一直持续下去,直到顺序扫描完目标索引所有叶子块里的所有索引行。

  5. 上述顺序扫描操作完成后,聚簇因子的当前值就是索引统计信息中的CLUSTERING_FACTOR,Oracle会将其存储在数据字典里。

从上述聚簇因子的算法可以知道,如果聚簇因子的值接近对应表的表块的数量,则说明目标索引索引行和存储于对应表中数据行的存储顺序相似程度非常高。这也就意味着Oracle走索引范围扫描后取得目标rowid再回表去访问对应表块的数据时,相邻的索引行所对应的rowid极有可能处于同一个表块中,即Oracle在通过索引行记录的rowid回表第一次读取对应的表块并将该表块缓存在buffer cache中后,当再通过相邻索引行记录的rowid回表第二次读取对应的表块时,就不需要再产生物理I/O了,因为这次要访问的和上次已经访问过的表块是同一个块,Oracle已经将其缓存在了buffer cache中。而如果聚簇因子的值接近对应表的记录数,则说明目标索引索引行和存储于对应表中数据行的存储顺序和相似程度非常低,这也就意味着Oracle走索引范围扫描取得目标rowid再回表去访问对应表块的数据时,相邻的索引行所对应的rowid极有可能不处于同一个表块中,即Oracle在通过索引行记录的rowid回表第一次去读取对应的表块并将表块缓存在buffer cache中后,当再通过相邻索引行记录的rowid回表第二次读取对应的表块时,还需要再产生物理I/O,因为这次要访问的和上次已经访问过的表块并不是同一个块。

换句话说,聚簇因子高的索引走索引范围扫描时比相同条件下聚簇因子低的索引要耗费更多的物理I/O,所以聚簇因子高的索引走索引范围扫描的成本会比相同条件下聚簇因子低的索引走索引范围扫描的成本高。

这里构造一个非常极端的例子,全索引中没有任何相邻的索引行记录的rowid指向表中相同的数据块:

wKiom1ixjzXA39l-AAIWFY4GW9U506.png

根据上述聚簇因子的算法,我们可以算出此索引IDX_T1的聚簇因子的值应是20。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
zx@MYDB> create  table  t1 (id number, name  char (1200));
 
Table  created.
 
zx@MYDB> insert  into  t1  values (1, '1' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (3, '3' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (5, '5' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (7, '7' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (9, '9' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (11, '11' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (13, '13' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (15, '15' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (17, '17' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (19, '19' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (2, '2' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (4, '4' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (6, '6' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (8, '8' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (10, '10' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (12, '12' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (14, '14' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (16, '16' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (18, '18' );
 
1 row created.
 
zx@MYDB> insert  into  t1  values (20, '20' );
 
1 row created.
 
zx@MYDB> commit ;
 
Commit  complete.
 
zx@MYDB> create  index  idx_t1  on  t1(id);
 
Index  created.
 
zx@MYDB>col location  for  a10
zx@MYDB> select  id,dbms_rowid.rowid_relative_fno(rowid)|| '_' ||dbms_rowid.rowid_block_number(rowid) location  from  t1  order  by  location,id;
 
         ID LOCATION
---------- ----------
          1 4_300
          3 4_300
          5 4_300
          7 4_300
          9 4_300
         11 4_301
         13 4_301
         15 4_301
         17 4_301
         19 4_301
          2 4_302
          4 4_302
          6 4_302
          8 4_302
         10 4_302
         12 4_303
         14 4_303
         16 4_303
         18 4_303
         20 4_303
 
20  rows  selected.

从上述显示结果可以看出1、3、5、7、9在4号文件的300号数据块内,11、13、15、17、19在4号文件的301号数据块内,2、4、6、8、10在4号文件的第302号数据块内,12、14、16、18、20在4号文件的第303号数据块内。

收集统计信息并查看聚簇因子的值

1
2
3
4
5
6
7
8
9
10
#收集统计信息并查看聚簇因子的值
zx@MYDB> exec  dbms_stats.gather_table_stats(ownname=> 'ZX' ,tabname=> 'T1' ,method_opt=> 'for all columns size auto' , cascade => true ,estimate_percent=>100);
 
PL/SQL  procedure  successfully completed.
 
zx@MYDB> select  index_name,clustering_factor  from  dba_indexes  where  index_name= 'IDX_T1' ;
 
INDEX_NAME                                                                                 CLUSTERING_FACTOR
------------------------------------------------------------------------------------------ -----------------
IDX_T1                                                                                                    20


Oracle数据库中,能够降低目标索引的聚簇因子的唯一方法就是对表中的数据按照目标索引的索引键值排序后重新存储。需要注意的是,这种按某一个目标索引的索引键值排序后重新存储表中数据的方法确实可以降低该目标索引聚簇因子的值 ,但可能会同时增加该表上存在的其他索引值的聚簇因子的值。

将表T1的数据原封不动的照搬到表T2中,只不过表T2的数据在存储时已经按id列排好序了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
zx@MYDB> create  table  t2  as  select  from  t1  order  by  id;
 
Table  created.
 
zx@MYDB> create  index  idx_t2  on  t2(id);
 
Index  created.
 
zx@MYDB> select  id,dbms_rowid.rowid_relative_fno(rowid)|| '_' ||dbms_rowid.rowid_block_number(rowid) location  from  t2  order  by  location,id;
 
         ID LOCATION
---------- ----------
          1 4_171
          2 4_171
          3 4_171
          4 4_171
          5 4_171
          6 4_172
          7 4_172
          8 4_172
          9 4_172
         10 4_172
         11 4_173
         12 4_173
         13 4_173
         14 4_173
         15 4_173
         16 4_174
         17 4_174
         18 4_174
         19 4_174
         20 4_174
 
20  rows  selected.
 
zx@MYDB> exec  dbms_stats.gather_table_stats(ownname=> 'ZX' ,tabname=> 'T1' ,method_opt=> 'for all columns size auto' , cascade => true ,estimate_percent=>100);
 
PL/SQL  procedure  successfully completed.
 
zx@MYDB> select  index_name,clustering_factor  from  dba_indexes  where  index_name= 'IDX_T2' ;
 
INDEX_NAME                                                                                 CLUSTERING_FACTOR
------------------------------------------------------------------------------------------ -----------------
IDX_T2                                                                                                     4

重复与表T1相同的一系列的操作,从结果可以看出索引IDX_T2的聚簇因子降为了4。而相邻的数据也都在同一数据块中。

Oracle数据库里,CBO在计算索引范围扫描(Index Range Scan)的成本时会使用如下公式:

IRS Cost = I/O Cost + CPU Cost

I/O Cost的计算公式为:

I/O Cost = Index Access I/O Cost + Table Access I/O Cost

Index Access I/O Cost = BLEVEL + CEIL(#LEAF_BLOCKS * IX_SEL)

Table Access I/O Cost = CEIL(CLUSTERING_FACTOR * IX_SEL_WITH_FILTERS)

从这个公式可以推断走索引范围扫描的成本可以近似看作是和聚簇因子成正比,因此,聚簇因子值的大小实际上对CBO判断是否走相关的索引起着至关重要的作用。

演示一个例子,通过修改聚簇索引的值就让原本走索引范围扫描的执行计划变成了走全表扫描:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
zx@MYDB> create  table  t1  as  select  from  dba_objects;
 
Table  created.
 
zx@MYDB> create  index  idx_t1  on  t1(object_id);
 
Index  created.
 
zx@MYDB> select  clustering_factor  from  dba_indexes  where  index_name= 'IDX_T1' ;
 
CLUSTERING_FACTOR
-----------------
              1063
 
zx@MYDB> select  /*+ cluster_factor_expmple_1 */ object_id,object_name  from  t1  where  object_id  between  103  and  108;
 
  OBJECT_ID OBJECT_NAME
---------- ------------------------------
        103 MIGRATE$
        104 DEPENDENCY$
        105 ACCESS$
        106 I_DEPENDENCY1
        107 I_DEPENDENCY2
        108 I_ACCESS1
 
rows  selected.
 
zx@MYDB> select  from  table (dbms_xplan.display_cursor( null , null , 'all' ));
 
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  ga3jv3kwwwmx5, child number 0
-------------------------------------
select  /*+ cluster_factor_expmple_1 */ object_id,object_name  from  t1
where  object_id  between  103  and  108
 
Plan hash value: 50753647
 
--------------------------------------------------------------------------------------
| Id  | Operation                   |  Name    Rows   | Bytes | Cost (%CPU)|  Time      |
--------------------------------------------------------------------------------------
|   0 |  SELECT  STATEMENT            |        |       |       |     3 (100)|          |
|   1 |   TABLE  ACCESS  BY  INDEX  ROWID| T1     |     6 |   474 |     3   (0)| 00:00:01 |
|*  2 |    INDEX  RANGE SCAN          | IDX_T1 |     6 |       |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------
......省略部分输出

SQL走了索引范围扫描,成本值为3

使用Hint强制SQL走全表扫描:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
zx@MYDB> select  /*+  full (t1) */ object_id,object_name  from  t1  where  object_id  between  103  and  108;
 
  OBJECT_ID OBJECT_NAME
---------- ------------------------------
        103 MIGRATE$
        104 DEPENDENCY$
        105 ACCESS$
        106 I_DEPENDENCY1
        107 I_DEPENDENCY2
        108 I_ACCESS1
 
rows  selected.
 
zx@MYDB> select  from  table (dbms_xplan.display_cursor( null , null , 'all' ));
 
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  b7hjwuvmg2ncy, child number 0
-------------------------------------
select  /*+  full (t1) */ object_id,object_name  from  t1  where  object_id
between  103  and  108
 
Plan hash value: 3617692013
 
--------------------------------------------------------------------------
| Id  | Operation         |  Name  Rows   | Bytes | Cost (%CPU)|  Time      |
--------------------------------------------------------------------------
|   0 |  SELECT  STATEMENT  |      |       |       |   287 (100)|          |
|*  1 |   TABLE  ACCESS  FULL | T1   |     6 |   474 |   287   (1)| 00:00:04 |
--------------------------------------------------------------------------
......省略部分输出

现在SQL走全表扫描,成本值为287

我们已经知道走索引范围扫描的成本可以近似看作是和聚簇因子成正比,所以如果想让上述SQL的执行计划从索引范围扫描变为全表扫描,那么只需要调整聚簇因子的值,使走索引范围扫描的成本值大于走全表扫描的成本值346即可达到目的。

先将索引IDX_T1的聚簇因子的值手工调整为100万:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
zx@MYDB> exec  dbms_stats.set_index_stats(ownname=> 'ZX' ,indname=> 'IDX_T1' ,clstfct=>1000000,no_invalidate=> false );
 
PL/SQL  procedure  successfully completed.
 
zx@MYDB> select  clustering_factor  from  dba_indexes  where  index_name= 'IDX_T1' ;
 
CLUSTERING_FACTOR
-----------------
           1000000
 
zx@MYDB> select  /*+ cluster_factor_expmple_2 */ object_id,object_name  from  t1  where  object_id  between  103  and  108;
 
  OBJECT_ID OBJECT_NAME
---------- ------------------------------
        103 MIGRATE$
        104 DEPENDENCY$
        105 ACCESS$
        106 I_DEPENDENCY1
        107 I_DEPENDENCY2
        108 I_ACCESS1
 
rows  selected.
 
zx@MYDB> select  from  table (dbms_xplan.display_cursor( null , null , 'all' ));
 
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  1ucqsj4j0j432, child number 0
-------------------------------------
select  /*+ cluster_factor_expmple_2 */ object_id,object_name  from  t1
where  object_id  between  103  and  108
 
Plan hash value: 50753647
 
--------------------------------------------------------------------------------------
| Id  | Operation                   |  Name    Rows   | Bytes | Cost (%CPU)|  Time      |
--------------------------------------------------------------------------------------
|   0 |  SELECT  STATEMENT            |        |       |       |   105 (100)|          |
|   1 |   TABLE  ACCESS  BY  INDEX  ROWID| T1     |     6 |   474 |   105   (0)| 00:00:02 |
|*  2 |    INDEX  RANGE SCAN          | IDX_T1 |     6 |       |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------------------
......省略部分输出

从执行计划中可以看出,成本已经由3变为105(即增加了102),这说明我们对索引IDX_T1的聚簇因子的调整生效了。

要使成本值大于287,只需要把聚簇因子的值调整到400万。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
zx@MYDB> exec  dbms_stats.set_index_stats(ownname=> 'ZX' ,indname=> 'IDX_T1' ,clstfct=>4000000,no_invalidate=> false );
 
PL/SQL  procedure  successfully completed.
 
zx@MYDB> select  clustering_factor  from  dba_indexes  where  index_name= 'IDX_T1' ;
 
CLUSTERING_FACTOR
-----------------
           4000000
 
zx@MYDB> select  /*+ cluster_factor_expmple_3 */ object_id,object_name  from  t1  where  object_id  between  103  and  108;
 
  OBJECT_ID OBJECT_NAME
---------- ------------------------------
        103 MIGRATE$
        104 DEPENDENCY$
        105 ACCESS$
        106 I_DEPENDENCY1
        107 I_DEPENDENCY2
        108 I_ACCESS1
 
rows  selected.
 
zx@MYDB> select  from  table (dbms_xplan.display_cursor( null , null , 'all' ));
 
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID  cwkc8q61bypa6, child number 0
-------------------------------------
select  /*+ cluster_factor_expmple_3 */ object_id,object_name  from  t1
where  object_id  between  103  and  108
 
Plan hash value: 3617692013
 
--------------------------------------------------------------------------
| Id  | Operation         |  Name  Rows   | Bytes | Cost (%CPU)|  Time      |
--------------------------------------------------------------------------
|   0 |  SELECT  STATEMENT  |      |       |       |   287 (100)|          |
|*  1 |   TABLE  ACCESS  FULL | T1   |     6 |   474 |   287   (1)| 00:00:04 |
--------------------------------------------------------------------------

从上面显示的内容可以看出执行计划从索引范围扫描变成了全表扫描。


参考《基于Oracle的SQL优化》




     本文转自hbxztc 51CTO博客,原文链接:http://blog.51cto.com/hbxztc/1901258,如需转载请自行联系原作者




相关文章
|
12小时前
|
SQL Oracle 关系型数据库
Oracle-index索引解读
Oracle-index索引解读
77 0
|
11小时前
|
存储 Oracle 关系型数据库
Oracle索引知识看这一篇就足够
Oracle索引知识看这一篇就足够
|
11小时前
|
存储 Oracle 关系型数据库
Oracle 12c的多重索引:数据的“多维导航仪”
【4月更文挑战第19天】Oracle 12c的多重索引提升数据查询效率,如同多维导航仪。在同一表上创建针对不同列的多个索引,加速检索过程。虽然过多索引会增加存储和维护成本,但合理选择和使用索引策略,结合位图、函数索引等高级特性,能优化查询,应对复杂场景。数据管理员应善用这些工具,根据需求进行索引管理,支持企业数据分析。
|
6月前
|
索引
Oracle-序列、索引和同义词
Oracle-序列、索引和同义词
27 0
|
12小时前
|
SQL Oracle 关系型数据库
[Oracle]索引
[Oracle]索引
68 0
[Oracle]索引
|
6月前
|
存储 SQL Oracle
Oracle优化避免索引失效
Oracle优化避免索引失效
208 0
|
7月前
|
存储 Oracle 关系型数据库
9-6 Oracle 管理索引
9-6 Oracle 管理索引
|
12月前
|
SQL Oracle 关系型数据库
Oracle-表分析和索引分析解读
Oracle-表分析和索引分析解读
145 0
|
存储 SQL Oracle
Oracle索引
Oracle索引
82 0
|
SQL 存储 Oracle
Oracle的视图,索引,约束,事务,数据库范式
🍅程序员小王的博客:程序员小王的博客 🍅 欢迎点赞 👍 收藏 ⭐留言 📝 🍅 如有编辑错误联系作者,如果有比较好的文章欢迎分享给我,我会取其精华去其糟粕 🍅java自学的学习路线:java自学的学习路线
81 0
Oracle的视图,索引,约束,事务,数据库范式