轉載- Oracle 聚簇因子(Clustering factor)

https://blog.csdn.net/robinson_0612/article/details/8847959;


    聚簇因子是 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、差的索引、差的聚簇因子的情形

       

 

5、影響聚簇因子的情形
    當插入到表的數據與索引的順序相同時,可以提高聚簇因子(接近表上的塊數)。
    因此,任意影響該順序的情形都將導致索引列上的聚簇因子變差。
    如列的順序,反向索引,空閒列表或空閒列表組。

 

6、提高聚簇因子
    堆表的數據存儲是無序存儲,因此需要使無序變爲有序。下面是提高聚簇因子的辦法。
    a、對於表上的多個索引以及組合索引的情形,索引的創建應考慮按應該按照經常頻繁讀取的大範圍數據的讀取順序來創建索引。
    b、定期重構表(針對堆表),也就是使得表與索引上的數據順序更接近。注意,是重構表,而不是重建索引。
       重建索引並不能顯劇提高CF的值,因爲索引列通常是有序的,無序的是原始表上的數據。
       提取原始表上的數據到一個臨時表,禁用依賴於該表的相關約束,truncate原始表,再將臨時表的數據按索引訪問順序填充到原始表。

    c、使用聚簇表來代替堆表。

 

7、實戰聚簇因子隨索引結構變化的情形

a、演示環境
scott@SYBO2SZ> select * from v$version where rownum<2;
 
BANNER
----------------------------------------------------------------
Oracle Database 10g Release 10.2.0.3.0 - 64bit Production
 
b、列順序對CF的影響
--列順序指索引列值順序與表中的列值的順序,一致,則CF良好,不一致,CF較差。
scott@SYBO2SZ> create table t as select * from dba_objects order by object_name;
 
scott@SYBO2SZ> create index i_obj_name on t(object_name);  -->基於object_name列創建索引
 
scott@SYBO2SZ> create index i_obj_id on t(object_id);      -->基於object_id列創建索引
 
cott@SYBO2SZ> exec dbms_stats.gather_table_stats('SCOTT','T',cascade=>true);
 
PL/SQL procedure successfully completed.
 
scott@SYBO2SZ> @idx_stat
Enter value for input_table_name: T
Enter value for owner: SCOTT
 
                                         AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME       LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED        TB_BLKS    TB_ROWS
---- ------------- ---------- ---------- ------------- ------------- ---------- ----------------- ---------- ----------
   1 I_OBJ_NAME           241      29476             1             1        675 20130418 17:00:42        695      48931
   1 I_OBJ_ID             108      48931             1             1      24887 20130418 17:06:10        695      48931
 
--從上面的查詢可以看出,索引I_OBJ_NAME的聚簇因子小於表上的塊數,一個良好的CF值,因爲object_name列是有序插入的。
--而索引I_OBJ_ID上的CF接近於表上行數的一半,說明該索引上的CF值不是很理想,因爲object_id在插入到table時是無序的。
--從上可知,一個表只能有一種有序的方式來組織數據。因此對於多出一個索引的表,且順序按照非插入時的順序時,則其他索引上的聚簇因子很難獲得理想的值。
 
c、組合索引對CF的影響
--對於組合索引,列的順序影響聚簇因子的大小
 
--我們創建如下組合索引
 
scott@SYBO2SZ> create index i_obj_name_id on t (object_name, object_id);
 
scott@SYBO2SZ> create index i_obj_id_name on t (object_id, object_name);
 
scott@SYBO2SZ> exec dbms_stats.gather_table_stats('SCOTT','T',cascade=>true)
 
PL/SQL procedure successfully completed.
 
scott@SYBO2SZ> @idx_stat
Enter value for input_table_name: T
Enter value for owner: SCOTT
 
                                                     AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME          LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED        TB_BLKS    TB_ROWS
---- ---------------- ---------- ---------- ------------- ------------- ---------- ----------------- ---------- ----------
   1 I_OBJ_NAME              241      29476             1             1        675 20130418 17:17:17        695      48931
   1 I_OBJ_ID                108      48931             1             1      24887 20130418 17:17:17        695      48931
   1 I_OBJ_NAME_ID           274      48931             1             1        945 20130418 17:17:17        695      48931
   1 I_OBJ_ID_NAME           274      48931             1             1      24887 20130418 17:17:18        695      48931
 
--從上面的結果可知,
--新創建的組合索引,I_OBJ_NAME_ID(object_name, object_id),object_name是前導列,因此CF值儘管比單列是大,依然表現良好。
--而索引I_OBJ_ID_NAME(object_id, object_name),object_id作爲前導列,CF值與單列索引I_OBJ_ID相同。
--上面的四個索引來看,無論是單列還是符合索引,當索引列(leaf)的順序接近於表上行的順序,CF表現良好。
 
d、反向索引對CF的影響
--反轉索引主要是重新分佈索引值,也就是將相連比較緊密地索引鍵值分散到不同或相距比較遠的快上以避免競爭。
--下面基於表t來新創建表t2
scott@SYBO2SZ> create table t2 nologging as select * from t;
 
scott@SYBO2SZ> create index i_obj_name_reverse on t2(object_name) reverse;  -->創建反向索引
 
scott@SYBO2SZ> exec dbms_stats.gather_table_stats('SCOTT','T2',cascade=>true)
 
PL/SQL procedure successfully completed.
 
scott@SYBO2SZ> @idx_stat
Enter value for input_table_name: T2
Enter value for owner: SCOTT
 
                                              AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME            LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED        TB_BLKS    TB_ROWS
---- ------------------ ---------- ---------- ------------- ------------- ---------- ----------------- ---------- ----------
   1 I_OBJ_NAME_REVERSE        241      29476             1             1      28104 20130418 17:22:49        695      48931
 
--上面創建的反向索引的CF較之前的都要大,因索引鍵上的值是反向的,也就是說是無序的。
 
--在段空間管理基於手動管理的方式下,如果使用freelist可以避免段操作上DML的競爭,但索引列上將具有較比較糟糕的聚簇因子(演示省略)
 
8、實戰聚簇因子隨DML變化的情形

a、創建演示環境
scott@SYBO2SZ> @cr_big_tb 1000000  -->創建一張百萬記錄的表
 
Table created.
 
scott@SYBO2SZ> @idx_stat           -->查看錶與索引相關信息(CF爲14489,TB_BLKS爲14652)
Enter value for input_table_name: BIG_TABLE
Enter value for owner: SCOTT
 
                                                     AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME       LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED         TB_BLKS    TB_ROWS
---- ------------- ---------- ---------- ------------- ------------- ---------- ------------------ ---------- ----------
   2 BIG_TABLE_PK        2088    1000000             1             1      14489 20130422 12:27:43       14652     999712
 
b、模擬DML操作
--創建一個臨時表來存儲將要從表big_table刪除的記錄
scott@SYBO2SZ> create table big_table_tmp nologging as select * from big_table where id>=10000 and id<=200000;
 
scott@SYBO2SZ> delete from big_table nologging where id>=10000 and id<=200000;  -->從表big_table刪除一些記錄
 
scott@SYBO2SZ> commit;     
 
-->查看錶與索引相關信息(從下面的查詢結果可知,刪除記錄並不使得CF發生變化)
scott@SYBO2SZ> @idx_stat     
Enter value for input_table_name: BIG_TABLE
Enter value for owner: SCOTT
 
                                          AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME        LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED         TB_BLKS    TB_ROWS
---- -------------- ---------- ---------- ------------- ------------- ---------- ------------------ ---------- ----------
   2 BIG_TABLE_PK         2088    1000000             1             1      14489 20130422 12:27:43       14652     999712
 
scott@SYBO2SZ> exec dbms_stats.gather_table_stats('SCOTT','BIG_TABLE',cascade=>true);   -->收集統計信息
 
scott@SYBO2SZ> @idx_stat          -->查看錶與索引相關信息(在收集統計信息後,刪除記錄後CF爲11732,TB_BLKS依然爲14652)
Enter value for input_table_name: BIG_TABLE            --(TB_BLKS塊數未發生變化是因爲空閒空間沒有釋放,需要shrink)
Enter value for owner: SCOTT
 
                                         AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME       LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED         TB_BLKS    TB_ROWS
---- ------------- ---------- ---------- ------------- ------------- ---------- ------------------ ---------- ----------
   2 BIG_TABLE_PK        1692     809999             1             1      11732 20130422 12:31:45       14652     808497
 
-->接下來將刪除的數據插入到big_table以模擬表上新增數據,分兩次插入,以使得id變得無序   
scott@SYBO2SZ> insert into big_table nologging select * from big_table_tmp where id>=150000 and id<=200000
  2  order by object_name;
  
scott@SYBO2SZ> insert into big_table nologging select * from big_table_tmp where id>=10000 and id<150000
  2  order by object_name;
 
scott@SYBO2SZ> exec dbms_stats.gather_table_stats('SCOTT','BIG_TABLE',cascade=>true);  -->收集統計信息
 
scott@SYBO2SZ> @idx_stat     -->查看錶與索引相關信息(此時CF的值由原來的14489增大到114256,呈數量級變化)
Enter value for input_table_name: BIG_TABLE
Enter value for owner: SCOTT
 
                                         AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME       LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED         TB_BLKS    TB_ROWS
---- ------------- ---------- ---------- ------------- ------------- ---------- ------------------ ---------- ----------
   2 BIG_TABLE_PK        2088    1000000             1             1     114256 20130422 12:33:31       14652     998513
   
--下面嘗試move table是否對CF有向影響
scott@SYBO2SZ> alter table big_table move;    
 
scott@SYBO2SZ> @idx_stat   -->查看錶與索引相關信息(move table之後,無任何變化)
Enter value for input_table_name: BIG_TABLE
Enter value for owner: SCOTT
 
                                          AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME        LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED         TB_BLKS    TB_ROWS
---- -------------- ---------- ---------- ------------- ------------- ---------- ------------------ ---------- ----------
   2 BIG_TABLE_PK         2088    1000000             1             1     114256 20130422 12:33:31       14652     998513
 
-->嘗試收集統計信息後,在看CF的變化
-->下面的錯誤表明,move之後,索引失效
scott@SYBO2SZ> exec dbms_stats.gather_table_stats('SCOTT','BIG_TABLE',cascade=>true);
BEGIN dbms_stats.gather_table_stats('SCOTT','BIG_TABLE',cascade=>true); END;
 
*
ERROR at line 1:
ORA-20000: index "SCOTT"."BIG_TABLE_PK"  or partition of such index is in unusable state
ORA-06512: at "SYS.DBMS_STATS", line 13182
ORA-06512: at "SYS.DBMS_STATS", line 13202
ORA-06512: at line 1
 
scott@SYBO2SZ> alter index big_table_pk rebuild nologging;   ---->重建索引
 
scott@SYBO2SZ> exec dbms_stats.gather_table_stats('SCOTT','BIG_TABLE',cascade=>true);  -->再次收集統計信息
 
PL/SQL procedure successfully completed.
 
scott@SYBO2SZ> @idx_stat  -->重建索引後,CF的值反而增大了
Enter value for input_table_name: BIG_TABLE
Enter value for owner: SCOTT
 
                                         AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME       LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED         TB_BLKS    TB_ROWS
---- ------------- ---------- ---------- ------------- ------------- ---------- ------------------ ---------- ----------
   2 BIG_TABLE_PK        2088    1000000             1             1     118384 20130422 12:36:31       14649     999427
 
c、重建big_table
-->下面通過重建big_table來縮小CF的值,新的表名爲big_table_tmp
scott@SYBO2SZ> drop table big_table_tmp purge;  --->刪除之前的臨時表
 
scott@SYBO2SZ> create table big_table_tmp nologging as select * from big_table order by id;
 
scott@SYBO2SZ> create unique index big_table_tmp_pk on big_table_tmp(id);
 
scott@SYBO2SZ> alter table big_table_tmp add constraint big_table_tmp_pk primary key(id) using index big_table_tmp_pk;
 
scott@SYBO2SZ> exec dbms_stats.gather_table_stats('SCOTT','BIG_TABLE_TMP',cascade=>true);
 
scott@SYBO2SZ> @idx_stat    --->表big_table_tmp上的CF值(14486)小於原始的CF值(14489)
Enter value for input_table_name: big_table_tmp
Enter value for owner: scott
 
--Author : Robinson
--Blog   : http://blog.csdn.net/robinson_0612
 
                                            AVG LEAF BLKS AVG DATA BLKS
BLEV IDX_NAME          LEAF_BLKS   DST_KEYS       PER KEY       PER KEY CLUST_FACT LAST_ANALYZED         TB_BLKS    TB_ROWS
---- ---------------- ---------- ---------- ------------- ------------- ---------- ------------------ ---------- ----------
   2 BIG_TABLE_TMP_PK       2088    1000000             1             1      14486 20130422 12:38:37       14649     995891
 
d、比較不同的CF對查詢性能的影響
-->下面來基於表big_table與big_table_tmp來比較一下不同的CF對查詢的影響
scott@SYBO2SZ> set autot trace;
scott@SYBO2SZ> select * from big_table where id between 10000 and 15000;
 
5001 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 3747652938
 
--------------------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |              |  5001 |   478K|   606   (0)| 00:00:08 |
|   1 |  TABLE ACCESS BY INDEX ROWID| BIG_TABLE    |  5001 |   478K|   606   (0)| 00:00:08 |
|*  2 |   INDEX RANGE SCAN          | BIG_TABLE_PK |  5001 |       |    13   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("ID">=10000 AND "ID"<=15000)
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
       2993  consistent gets
        531  physical reads
        116  redo size
     287976  bytes sent via SQL*Net to client
       4155  bytes received via SQL*Net from client
        335  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       5001  rows processed
 
--原始表上的查詢的cost爲606, consistent gets與physical reads分別爲2993,531
 
scott@SYBO2SZ> select * from big_table_tmp where id between 10000 and 15000;
 
5001 rows selected.
 
Execution Plan
----------------------------------------------------------
Plan hash value: 1127920103
 
------------------------------------------------------------------------------------------------
| Id  | Operation                   | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                  |  4982 |   476K|    86   (0)| 00:00:02 |
|   1 |  TABLE ACCESS BY INDEX ROWID| BIG_TABLE_TMP    |  4982 |   476K|    86   (0)| 00:00:02 |
|*  2 |   INDEX RANGE SCAN          | BIG_TABLE_TMP_PK |  4982 |       |    13   (0)| 00:00:01 |
------------------------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   2 - access("ID">=10000 AND "ID"<=15000)
 
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
        750  consistent gets
         76  physical reads
          0  redo size
     287976  bytes sent via SQL*Net to client
       4155  bytes received via SQL*Net from client
        335  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       5001  rows processed
 
--新創建的表的cost 爲86, consistent gets與physical reads分別爲750,76. 呈數量級低低於原表的開銷
             
-->可以將原始表big_table上的數據刪除(truncate),刪除前禁用依賴於該表的所有約束,然後將big_table_tmp的數據使用order by插入到big_table
-->註上面的create table as ..方式並不適合用於生產環境的真實操作,因爲表上的一些屬性會被忽略掉.
 
9、小結
  a、任意情形下(堆表),表上數據的存儲只能按照一種特定的順序進行存儲。
  b、由上面的特性決定了表上的只有一個特定的索引列(單索引或組合索引)具有最佳的CF值。
  c、索引的創建應考慮按應該按照經常頻繁讀取的大範圍數據的讀取順序來創建索引,以保證得到最佳的CF值。
  d、索引在被創建之時,基於該索引列上的CF值即被產生,但表上的DML操作後需要收集統計信息纔可以更新CF的值。
  e、基於表上頻繁的DML操作,尤其是delete後再新增記錄,可用空閒空間被填充,將使得CF的值呈增大趨勢。
  f、alter table move tabname並不會影響CF的值,該功能只是移動高水位線,且不釋放空間。
  g、重建索引對CF的值收效甚微,因爲原始表數據存儲順序未發生根本變化。
  h、CF的值是影響查詢分析器對執行計劃的評估與生成的因素之一(即是否走索引還是全表掃描,嵌套連接時哪個表爲驅動表等)。
  i、通過重建表或使用聚簇表來改進CF的值,建議將原始表數據填充到臨時表,禁用依賴於該表的所有約束後truncate該表,再從臨時表導回數據(按順序),啓用約束。
  j、不推薦使用create table as select(CTAS),因爲表上的一些特性會被忽略,可以用SQL*Plus copy方式來實現。
--------------------- 
作者:Leshami 
來源:CSDN 
原文:https://blog.csdn.net/leshami/article/details/8847959 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章