Oracle中組合索引的使用詳解

聽海★藍心夢發表於2009-03-13
 在Oracle中可以建立組合索引,即同時包含兩個或兩個以上列的索引。在組合索引的使用方面,Oracle有以下特點:

    1、 當使用基於規則的最佳化器(RBO)時,只有當組合索引的前導列出現在SQL語句的where子句中時,才會使用到該索引;

    2、 在使用Oracle9i之前的基於成本的最佳化器(CBO)時, 只有當組合索引的前導列出現在SQL語句的where子句中時,才可能會使用到該索引,這取決於最佳化器計算的使用索引的成本和使用全表掃描的成本,Oracle會自動選擇成本低的訪問路徑(請見下面的測試1和測試2);

    3、 從Oracle9i起,Oracle引入了一種新的索引掃描方式——索引跳躍掃描(index skip scan),這種掃描方式只有基於成本的最佳化器(CBO)才能使用。這樣,當SQL語句的where子句中即使沒有組合索引的前導列,並且索引跳躍掃描的成本低於其他掃描方式的成本時,Oracle就會使用該方式掃描組合索引(請見下面的測試3);

    4、 Oracle最佳化器有時會做出錯誤的選擇,因為它再“聰明”,也不如我們SQL語句編寫人員更清楚表中資料的分佈,在這種情況下,透過使用提示(hint),我們可以幫助Oracle最佳化器作出更好的選擇(請見下面的測試4)。

    關於以上情況,我們分別測試如下:

    我們建立測試表T,該表的資料來源於Oracle的資料字典表all_objects,表T的結構如下:
SQL> desc t
---------------------------- --------------- --------------------------------------
名稱                                 是否為空?   型別
---------------------------- --------------- --------------------

OWNER                          
NOT NULL     VARCHAR2(30)

OBJECT_NAME            NOT NULL     VARCHAR2(30)

SUBOBJECT_NAME                            
VARCHAR2(30)

OBJECT_ID                  NOT NULL      NUMBER

DATA_OBJECT_ID                               
NUMBER

OBJECT_TYPE                                     
VARCHAR2(18)

CREATED                   
NOT NULL      DATE

LAST_DDL_TIME       
NOT NULL      DATE

TIMESTAMP                                        VARCHAR2(19)

STATUS                                                
VARCHAR2(7)

TEMPORARY                                      VARCHAR2(1)

GENERATED                                    
VARCHAR2(1)

SECONDARY                                    
VARCHAR2(1)


表中的資料分佈情況如下:

SQL
> select object_type,count(*) from t group by object_type;

OBJECT_TYPE
COUNT(*)

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

CONSUMER
GROUP 20

EVALUATION CONTEXT
10

FUNCTION 360

INDEX 69

LIBRARY
20

LOB
20

OPERATOR
20

PACKAGE
1210

PROCEDURE 130

SYNONYM
16100

TABLE 180

TYPE
2750

VIEW 8600

已選擇13行。

SQL
> select  count(*)  from  t;

COUNT(*)

----------

29489

    我們在表T上建立如下索引並對其進行分析:
現在讓我們編寫幾條SQL語句來測試一下Oracle最佳化器對訪問路徑的選擇:

    測試1)
SQL> set autotrace traceonly

SQL
> SELECT * FROM T WHERE OBJECT_TYPE='LOB';

已選擇20行。

Execution
Plan

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

0 SELECT STATEMENT Optimizer=CHOOSE (Cost=22 Card=20 Bytes=1740)

1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=22 Card=20 Bytes=1740)

2 1 INDEX (RANGE SCAN) OF 'INDX_T' (NON-UNIQUE) (Cost=2 Card=20)

    正如我們所期望的,由於使用了組合索引的前導列並且訪問了表中的少量記錄,Oracle明智地選擇了索引掃描。那麼,如果我們訪問表中的大量資料時,Oracle會選擇什麼樣的訪問路徑呢?請看下面的測試:

    測試2)
SQL> SELECT * FROM T WHERE OBJECT_TYPE='SYNONYM';

已選擇16100行。

Execution
Plan

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

0 SELECT STATEMENT Optimizer=CHOOSE (Cost=38 Card=16100 Bytes=1400700)

1 0 TABLE ACCESS (FULL) OF 'T' (Cost=38 Card=16100 Bytes=1400700)

Statistics

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

0 recursive calls

0 db block gets

1438 consistent gets

13 physical reads

0 redo size

941307 bytes sent via SQL*Net to client

12306 bytes received via SQL*Net from client

1075 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

16100 rows processed

    很明顯,即使使用了組合索引的前導列,但是由於訪問了表中的大量資料,Oracle選擇了不使用索引而直接使用全表掃描,因為最佳化器認為全表掃描的成本更低,但事實是不是真的這樣的?我們透過增加提示(hint)來強制它使用索引來看看:
SQL> SELECT/*+ INDEX (T INDX_T)*/ * FROM T WHERE OBJECT_TYPE='SYNONYM';

已選擇16100行。

Execution
Plan
----------------------------------------------------------
0 SELECT STATEMENT Optimizer=CHOOSE (Cost=16180 Card=16100 Bytes=1400700)

1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=16180 Card=16100 Bytes=1400700)

2 1 INDEX (RANGE SCAN) OF 'INDX_T' (NON-UNIQUE) (Cost=80 Card=16100)

Statistics

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

0 recursive calls

0 db block gets

17253 consistent gets

16 physical reads

0 redo size

298734 bytes sent via SQL*Net to client

12306 bytes received via SQL*Net from client

1075 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

16100 rows processed

    從以上結果可以看出,在訪問大量資料的情況下,使用索引確實會導致更高的執行成本,這從statistics部分的邏輯讀取數(consistent gets)就可以看出,使用索引導致的邏輯讀取數是不使用索引導致的邏輯讀的10倍還多。因此,Oracle明智地選擇了全表掃描而不是索引掃描。
下面,讓我們來看看where子句中沒有索引前導列的情況:

    測試3)
 
SQL> select * from t where object_name= 'DEPT';

已選擇10行。

Execution
Plan

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

0 SELECT STATEMENT Optimizer=CHOOSE (Cost=29 Card=14 Bytes=1218)

1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=29 Card=14 Bytes=1218)

2 1 INDEX (SKIP SCAN) OF 'INDX_T' (NON-UNIQUE) (Cost=14 Card=14)

Statistics

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

0 recursive calls

0 db block gets

24 consistent gets

0 physical reads

0 redo size

1224 bytes sent via SQL*Net to client

503 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

10 rows processed

    OK!由於只查詢了10條資料,即使沒有使用前導列,Oracle正確地選擇了索引跳躍掃描。我們再來看看如果不使用索引跳躍掃描,該語句的成本:

SQL> select/*+ NO_INDEX(T INDX_T)*/ * from t where object_name= 'DEPT';

已選擇10行。

Execution
Plan

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

0 SELECT STATEMENT Optimizer=CHOOSE (Cost=38 Card=14 Bytes=1218)

1 0 TABLE ACCESS (FULL) OF 'T' (Cost=38 Card=14 Bytes=1218)

Statistics

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

0 recursive calls

0 db block gets

375 consistent gets

17 physical reads

0 redo size

1224 bytes sent via SQL*Net to client

503 bytes received via SQL*Net from client

2 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

10 rows processed
 
    正如我們所料,不使用索引所導致的邏輯讀(375)確實比使用索引的邏輯讀多(24),達到10倍以上。
 

繼續我們的測試,現在我們來看看Oracle不選擇使用索引的情況:

    測試4)
 
SQL> select * from t where object_name LIKE 'DE%';

已選擇180行。

Execution
Plan

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

0 SELECT STATEMENT Optimizer=CHOOSE (Cost=38 Card=37 Bytes=3219)

1 0 TABLE ACCESS (FULL) OF 'T' (Cost=38 Card=37 Bytes=3219)

Statistics

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

0 recursive calls

0 db block gets

386 consistent gets

16 physical reads

0 redo size

12614 bytes sent via SQL*Net to client

624 bytes received via SQL*Net from client

13 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

180 rows processed

    這次只選擇了180條資料,跟表T中總的資料量29489條相比,顯然只是很小的一部分,但是Oracle還是選擇了全表掃描,有386個邏輯讀。這種情況下,如果我們強制使用索引,情況會怎樣呢?
 
SQL> select/*+ INDEX(T INDX_T)*/ * from t where object_name LIKE 'DE%';

已選擇180行。

Execution
Plan

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

0 SELECT STATEMENT Optimizer=CHOOSE (Cost=182 Card=37 Bytes=3219)

1 0 TABLE ACCESS (BY INDEX ROWID) OF 'T' (Cost=182 Card=37 Bytes=3219)

2 1 INDEX (FULL SCAN) OF 'INDX_T' (NON-UNIQUE) (Cost=144 Card=37)

Statistics

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

0 recursive calls

0 db block gets

335 consistent gets

0 physical reads

0 redo size

4479 bytes sent via SQL*Net to client

624 bytes received via SQL*Net from client

13 SQL*Net roundtrips to/from client

0 sorts (memory)

0 sorts (disk)

180 rows processed

    透過新增提示(hint),我們強制Oracle使用了索引掃描(index full scan),執行了335個邏輯讀,比使用全表掃描的時候少了一些。

    由此可見,Oracle最佳化器有時會做出錯誤的選擇,因為它再“聰明”,也不如我們SQL語句編寫人員更清楚表中資料的分佈,在這種情況下,透過使用提示(hint),我們可以幫助Oracle最佳化器作出更好的選擇。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/751371/viewspace-567129/,如需轉載,請註明出處,否則將追究法律責任。

相關文章