【轉】聚簇因子

聚簇因子sql

咱們每每會討論何時用索引,何時用全表:你們可能說的最多的是須要的數據量和表裏面的數據量佔比,有些人說20%如下用索引,有些人說10%以上就不能用索引了,數據庫

其實除了和數據量有關還有一個很是關鍵因數就是聚簇因子。oracle

      什麼是聚簇因子?app

      咱們先看咱們的經常使用的表又叫堆表。堆表的最大特徵就是數據的存儲獨立性,即數據的存儲與數據值沒有任何關聯地被存儲在磁盤的任意位置上。從另一個側面來看,該特徵也就意味着爲了查詢咱們所須要的數據必然要在磁盤的多個位置上進行查找。dom

      所謂"任意位置"的深層含義是指可以把數據物理地存儲在磁盤上的方法多種多樣。然而,從另一個角度來看,隨機存儲方式就是數據所佔據的位置分散在不一樣的數據塊上。ide

      在這種存儲狀態下,查詢相同數據所執行的物理讀取數量會隨着查詢數據的分散程度而不一樣。例如,數據行1~10被分散存儲在10個數據塊與兩個數據塊相比較,雖然兩種狀況邏輯讀取的數據行數(都是10行)是相同的,但在物理(I/O)讀取的數據塊數上卻相差5倍。在關係型數據庫中,不論在何種狀況下,每次最少都須要讀取一個數據塊。儘管咱們每次要求讀取的是行,可是實際上每次讀取的倒是數據塊。所以,若是可以在內存中命中咱們所須要查詢的數據行,則在很大程度上就可以減小物理I/O的數量。儘管在不一樣系統環境下會略有一些差別,但在通常狀況下,從內存塊上查詢數據的速度比從磁盤塊上查詢數據的速度至少快30倍左右(有些可能數百倍),聚簇因子主要影響着索引的讀取。oop

      聚簇因子是指,按照索引列值進行了排序的索引行序和對應表中數據行序的類似程度。這就好似把孩子與父母的相像程度用數值來表示同樣。因爲聚簇因子大小對數據讀取效率有着直接的影響。假設數據存儲以下圖:性能

則index1訪問其中7行,須要2個塊,而index2訪問3行就須要3個塊,測試

可見index2的 聚簇因子是很很差的,咱們假想下:加入有一個表t有1000萬行,有10萬個塊,咱們有個provcode(省)是索引,provcode的distinct有32個,那麼若是咱們取其中的一個省份若是按照平均來計算是否是就是1/32的數據,這個比例接近3%,不少人認爲這個應該走provode索引,可是若是這個32個值是平均分佈,也就是說極可能致使咱們取其中一個省份,因爲他分佈在全部的數據塊裏面,致使咱們至關於要讀取整個表,這個性能是很是差的,這個時候全表就效果更好(這裏有多塊讀等因數)。改變聚簇因子的辦法不是創建什麼索引,而是改變數據的存儲方式,若是一個表的數據已經固定了,咱們怎麼去使他相對某一列的聚簇因子好呢,咱們能夠在插入的時候將數據進行那一列的order by ,這樣會使得數據是按照順序的插入。好的聚簇因子的例子:優化

上圖能夠看到clustering_factor和塊數是相等的,這個是最好狀況,最壞的狀況是clustering_factor等於num_rows。

對於究竟應當按照哪一個列的順序存儲數據,這個也須要和業務相關,有些咱們是控制不了的,可是咱們須要理解存儲順序對咱們讀取的影響,若是咱們想得到好的聚簇因子只須要按期對錶進行CTAS(create table …  as … order by )便可,不過表重構的代價也是不小的,並且表的重構操做也不是爲所欲爲的事情,因此咱們須要理解聚簇因子並很好的使用它。

 

一.  官網說明

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

       -- row 存儲的越有序,clustering factor 的值越低

       Theclustering factor is useful as a rough measure of thenumber 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 entries point 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的,在一個大的indexrange scan時,這樣爲了讀取這些rowid 指向的block,就須要一次有一次重複的去讀這些block。

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

       --當clustering factor 值低時,說明index keys(rowid) 是指向的記錄是存儲在相同的block裏,這樣去讀row時,只須要在同一個block裏讀取就能夠了。就能夠減小重複讀取block的次數。

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

 http://download.oracle.com/docs/cd/E11882_01/server.112/e16508/indexiot.htm#CNCPT89180

二. Index Clustering Factor說明

       在裏面沒有提到index Clustering Factor參數,因此這裏說明一下。

       簡單的說, IndexClustering Factor是經過一個索引掃描一張表,須要訪問的表的數據塊的數量,即對I/O的影響. 也表明索引鍵值存儲位置是否有序。

       (1)若是越有序,即相鄰的鍵值存儲在相同的block,那麼這時候ClusteringFactor 的值就越低。

       (2)若是不是頗有序,即鍵值是隨即的存儲在block上,這樣在讀取鍵值時,可能就須要一次又一次的去訪問相同的block,從而增長了I/O.

Clustering Factor 的計算方式以下:

(1)掃描一個索引(large index range scan)

(2)比較某行的rowid和前一行的rowid,若是這兩個rowid不屬於同一個數據塊,那麼cluster factor增長1

(3)整個索引掃描完畢後,就獲得了該索引的cluster factor。

       若是ClusteringFactor接近於表存儲的塊數,說明這張表是按照索引字段的順序存儲的。

       若是ClusteringFactor接近於行的數量,那說明這張表不是按索引字段順序存儲的。

       在計算索引訪問成本的時候,這個值十分有用。Clustering Factor乘以選擇性參數(selectivity )就是訪問索引的開銷。

       若是這個統計數據不能真實反映出索引的真實狀況,那麼可能會形成優化器錯誤的選擇執行計劃。另外若是某張表上的大多數訪問是按照某個索引作索引掃描,那麼將該表的數據按照索引字段的順序從新組織,能夠提升該表的訪問性能。 

三. 測試

3.1 模擬問題

--查看版本信息

SYS@anqing2(rac2)> select * fromv$version where rownum=1;

BANNER

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

Oracle Database 10g Enterprise EditionRelease 10.2.0.4.0 - Prod

--建立測試表

SYS@anqing2(rac2)> create table t as select * from dba_objects where 1=2;

Table created.

SYS@anqing2(rac2)> begin

 2  for i in 1..10 loop

 3  insert /*+append*/ into t select * from dba_objects order by i;

 4  commit;

 5  end loop;

 6  end;

 7  /

PL/SQL procedure successfully completed.

-- 這樣insert的緣由是保證數據存儲的無序性

SYS@anqing2(rac2)> select count(*) fromt;

 COUNT(*)

----------

   502720

--查看錶的大小

SYS@anqing2(rac2)> set wrap off

SYS@anqing2(rac2)> col owner for a10

SYS@anqing2(rac2)> col segment_name fora15

SYS@anqing2(rac2)> select owner, segment_name, blocks, extents,bytes/1024/1024||'M' "size" from dba_segments where owner='SYS' and segment_name='T';

OWNER     SEGMENT_NAME     BLOCKS    EXTENTS  size

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

SYS                                     T                     6912         69          54M

--在object_id上構建索引

SYS@anqing2(rac2)> create index idx_t_id on t(object_id);

Index created.

 SYS@anqing2(rac2)> select owner, segment_name, segment_type,blocks, extents,bytes/1024/1024||'M' "SIZE" from dba_segments where owner='SYS' and  segment_name=upper('idx_t_id');

owner  segment_name   segment_type    blocks   extents  size

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

SYS                 IDX_T_ID                INDEX      1152           24     9M

--在沒有收集相關的統計信息以前,咱們查看一下Index Clustering Factor

SYS@anqing2(rac2)> select owner,index_name, clustering_factor, num_rows from dba_indexes where owner='SYS' andindex_name='IDX_T_ID';

OWNER     INDEX_NAME   CLUSTERING_FACTOR   NUM_ROWS

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

SYS       IDX_T_ID               502720     502720

 --收集統計信息

SYS@anqing2(rac2)> exec dbms_stats.gather_table_stats('SYS','T',cascade => true);

PL/SQL procedure successfully completed.

--再次查看InexClustering Factor

SYS@anqing2(rac2)> select owner, index_name,clustering_factor, num_rows from dba_indexes where owner='SYS' and  index_name='IDX_T_ID';

OWNER     INDEX_NAME      CLUSTERING_FACTOR   NUM_ROWS

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

SYS       IDX_T_ID                502720     502720

--統計信息收集前和後,Clustering Factor 值不變,說在建立索引的時候,會收集表中中數據真正的行數。而且這裏的Clustering Factor 等於Num_rows,也也說明表的Clustering Factor 是無序的。

--查看一個肯定值,而後查看執行計劃

SYS@anqing2(rac2)> explain plan for select *from t where object_id=1501;

Explained.

SYS@anqing1(rac1)> select * fromtable(dbms_xplan.display);

PLAN_TABLE_OUTPUT

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

Plan hash value: 514881935

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

| id | operation             | name    | rows | bytes | cost (%cpu)| time     |

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

|   0| select statement         |        |  10 |  930 |    14  (0)| 00:00:01 |

|   1|  table accessby index rowid| t    |    10 |  930 |    14 (0)| 00:00:01 |

|*  2|   index range scan     | idx_t_id |    10 |      |    3   (0)| 00:00:01 |

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

Predicate Information (identified byoperation id):

PLAN_TABLE_OUTPUT

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

   2- access("OBJECT_ID"=1000)

--這裏走了索引,cost爲14

--查詢一個範圍的執行計劃

SYS@anqing1(rac1)> explain plan for select * from t where object_id>1000 and object_id<2000;

Explained.

SYS@anqing1(rac1)>  select * fromtable(dbms_xplan.display);

PLAN_TABLE_OUTPUT

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

Plan hash value: 1601196873

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

| Id | Operation         | Name |Rows  | Bytes | Cost(%CPU)| Time     |

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

|   0| SELECT STATEMENT  |      | 8884 |   806K|  1537  (2)| 00:00:19 |

|*  1|  TABLE ACCESSFULL| T    |  8884 |  806K|  1537   (2)| 00:00:19 |

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

Predicate Information (identified byoperation id):

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

PLAN_TABLE_OUTPUT

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

   1- filter("OBJECT_ID"<2000 AND "OBJECT_ID">1000)

13 rows selected.

--注意,object_id上是否索引的,但這裏並無使用索引而是使用了全表掃描

--刷新buffercache,而後查看SQL 執行的物理讀,這個是否全表掃描的

SYS@anqing1(rac1)> alter system flush buffer_cache;

System altered.

Elapsed: 00:00:00.24

SYS@anqing1(rac1)> set autot traceonlystat

SYS@anqing1(rac1)> select * from t where object_id>1000 and object_id<2000;

9990 rows selected.

Elapsed: 00:00:17.13-- 用了17秒

Statistics

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

         1  recursive calls

         0  db block gets

      7573  consistent gets

       6911  physical reads --物理讀

       984  redo size

    746085  bytes sent via SQL*Net toclient

      7715  bytes received via SQL*Netfrom client

       667  SQL*Net roundtrips to/fromclient

         0  sorts (memory)

         0  sorts (disk)

      9990  rows processed

--強制走索引,看執行計劃

SYS@anqing1(rac1)> set autot off

SYS@anqing1(rac1)> explain plan for select /*+ index(t idx_t_id) */ * from t where object_id>1000 and object_id<2000;

Explained.

Elapsed: 00:00:00.03

SYS@anqing1(rac1)> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

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

Plan hash value: 514881935

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

| Id | Operation                   |Name     | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0| SELECT STATEMENT            |          | 8884 |   806K|  8974  (1)| 00:01:48 |

|   1|  TABLE ACCESSBY INDEX ROWID| T        |  8884 |  806K|  8974   (1)| 00:01:48 |

|*  2|   INDEX RANGE SCAN          | IDX_T_ID |  8942 |      |    22   (0)| 00:00:01 |

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

Predicate Information (identified byoperation id):

PLAN_TABLE_OUTPUT

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

   2- access("OBJECT_ID">1000 AND "OBJECT_ID"<2000)

14 rows selected.

--強制走索引以後,這裏的使用了index range scan,可是裏的cost 變成了8974.而走全表掃描時,是1537.

--查看強制走索引的物理讀

SYS@anqing1(rac1)> alter system flushbuffer_cache;

System altered.

Elapsed: 00:00:00.13

SYS@anqing1(rac1)> select /*+ index(tidx_t_id) */  * from t where object_id>1000 and object_id<2000;

9990 rows selected.

Elapsed: 00:00:00.25

Statistics

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

         0  recursive calls

         0  db block gets

     10679  consistent gets

       154  physical reads

          0 redo size

    205626  bytes sent via SQL*Net toclient

      7715  bytes received via SQL*Netfrom client

       667  SQL*Net roundtrips to/fromclient

         0  sorts (memory)

         0  sorts (disk)

      9990  rows processed

--這裏的物理讀要比走索引低不少,可是Oracle 卻沒有使用索引。由於Oracle 認爲走索引的Cost 比 走全表掃描大。而是是大N倍。 而CBO 就是基於Cost 來決定執行計劃的。

       經過第二節裏的分析,對於索引的Cost,Oracle 是根據Clustering Factor參數來計算的,而咱們的數據Clustering Factor參數很高,數據存儲無序。 這就形成了Oracle 認爲走索引的cost 比全表掃描大。

3.2 解決問題

       經過上面的分析,能夠看出,要下降Clustering Factor才能解決問題,而要解決Clustering Factor,就須要從新對table表的存儲位置進行排序。

--重建table

SYS@anqing1(rac1)> create table tt as select * from t where 1=0;

Table created.

SYS@anqing1(rac1)> insert /*+append */ into tt select * from t order by object_id;

502720 rows created.

SYS@anqing1(rac1)> commit;

Commit complete.

SYS@anqing1(rac1)> truncate table t;

Table truncated.

SYS@anqing1(rac1)> insert /*+append */ into t select * from tt;

502720 rows created.

SYS@anqing1(rac1)> commit;

Commit complete.

--查看錶和索引的信息

SYS@anqing1(rac1)> select owner,segment_name, blocks, extents, bytes/1024/1024||'M' "size" fromdba_segments where owner='SYS' and segment_name='T';

OWNER     SEGMENT_NAME     BLOCKS    EXTENTS size

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

SYS                                      T                     6912         69     54M

SYS@anqing1(rac1)> select owner,segment_name, segment_type,blocks, extents, bytes/1024/1024||'M'"SIZE" from dba_segments where owner='SYS' andsegment_name=upper('idx_t_id');

OWNER SEGMENT_NAME  SEGMENT_TYPEBLOCKS  EXTENTS SIZE

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

SYS       IDX_T_ID                          INDEX         1024              23                   8M

SYS@anqing1(rac1)> select owner,index_name, clustering_factor, num_rows from dba_indexes where owner='SYS' andindex_name='IDX_T_ID';

OWNER     INDEX_NAME     CLUSTERING_FACTOR   NUM_ROWS

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

SYS       IDX_T_ID                 502720     502720

--對索引進行rebuild

SYS@anqing1(rac1)> alter index idx_t_id rebuild;       

Index altered.

--查看ClusteringFactor

SYS@anqing1(rac1)>  select owner, index_name, clustering_factor,num_rows from dba_indexes where owner='SYS' and index_name='IDX_T_ID';

OWNER    INDEX_NAME       CLUSTERING_FACTOR   NUM_ROWS

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

SYS       IDX_T_ID              6958     502720

--注意這裏的Factor,已經變成6958. 咱們收集一下表的統計信息,而後與表的block 進行一次比較。

SYS@anqing1(rac1)> exec dbms_stats.gather_table_stats('SYS','T',cascade => true);

PL/SQL procedure successfully completed.

 SYS@anqing1(rac1)> select blocks from dba_tables where table_name='T';

 BLOCKS

----------

  6896

--表T 實際使用的block是6896,Clustering Facter 是6958.基本仍是比較接近了。這也說明相鄰的row是存儲在相同的block裏。

--再次查看以前sql的執行計劃

SYS@anqing1(rac1)> set linesize 100

SYS@anqing1(rac1)> explain plan for select * from t where object_id>1000 and  object_id<2000;

Explained.

SYS@anqing1(rac1)> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT

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

Plan hash value: 514881935

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

| Id | Operation                   |Name     | Rows  | Bytes | Cost (%CPU)| Time     |

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

|   0| SELECT STATEMENT            |          | 8928 |   810K|   147  (1)| 00:00:02 |

|   1|  TABLE ACCESSBY INDEX ROWID| T        |  8928 |  810K|   147   (1)| 00:00:02 |

|*  2|   INDEX RANGE SCAN          | IDX_T_ID |  8944 |      |    22   (0)| 00:00:01 |

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

Predicate Information (identified by operationid):

PLAN_TABLE_OUTPUT

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

   2- access("OBJECT_ID">1000 AND "OBJECT_ID"<2000)

14 rows selected.

--注意這裏的cost已經將到了147. 性能提高仍是很是明顯。

SYS@anqing1(rac1)> set autot trace stat

SYS@anqing1(rac1)> set timing on

SYS@anqing1(rac1)> alter system flush  buffer_cache;

System altered.

Elapsed: 00:00:00.08

SYS@anqing1(rac1)> select * from t whereobject_id>1000 and object_id<2000;

9990 rows selected.

Elapsed: 00:00:00.25

Statistics

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

         1  recursive calls

         0  db block gets

      1473  consistent gets

       147  physical reads

         0  redo size

    205626  bytes sent via SQL*Net toclient

      7715  bytes received via SQL*Netfrom client

       667  SQL*Net roundtrips to/fromclient

         0  sorts (memory)

         0  sorts (disk)

      9990  rows processed

四. 小結

       經過以上說明和測試,能夠看到Clustering Factor 也是索引健康的一個重要判斷的標準。 其值越低越好。  它會影響CBO 選擇正確的執行計劃。可是要注意一點,Clustering Factor 老是趨勢與不斷惡化的。

       提到了一個索引的選擇性.    索引的選擇性是指索引列中不一樣值的數目與表中記錄數的比。若是一個表中有2000條記錄,表索引列有1980個不一樣的值,那麼這個索引的選擇性就是1980/2000=0.99。一個索引的選擇性越接近於1,這個索引的效率就越高。CBO的優化器通常不會使用選擇性很差的索引。

       如今舉一個例子來看下爲何索引的選擇性越高效率越高。通常索引裏會包含rowid和鍵值。 假設在字段name 上有索引,其值以下:

row1 dave

row2 dave

row3 dave

row4 dave

row5 anqing

       按以上6條記錄看,索引的選擇性=2/6=0.33. 在這種狀況下,若是咱們根據Dave 來查詢,那麼索引就返回5行rowid。若是是多表的netsed loop鏈接,那代價就會很大了。因此當索引的選擇性越低,這種掃描的代價越大。

       對於這種列,能夠說是數據傾斜。 對這種狀況,就須要收集列信息的直方圖(histogram)。讓CBO 在選擇執行計劃時獲得更多的信息,從而選擇正確的執行計劃。

相關文章
相關標籤/搜索