在Oracle中能夠建立組合索引,即同時包含兩個或兩個以上列的索引。ide
組合索引的使用存在着必定的侷限,只有在謂詞中出現所有索引列時才能使用效率最高的index unique scan, 不然謂詞中必須包含前導列,不然會走Index full scan或者FTS。測試
一、 當使用基於規則的優化器(RBO)時,只有當組合索引的前導列出如今SQL語句的where子句中時,纔會使用到該索引;
二、 在使用Oracle9i以前的基於成本的優化器(CBO)時,只有當組合索引的前導列出如今SQL語句的where子句中時,纔可能會使用到該索引,這取決於優化器計算的使用索引的成本和使用全表掃描的成本,Oracle會自動選擇成本低的訪問路徑;
三、 從Oracle9i起,Oracle引入了一種新的索引掃描方式——索引跳躍掃描(index skip scan),這種掃描方式只有基於成本的優化器(CBO)才能使用。這樣,當SQL語句的where子句中即便沒有組合索引的前導列,而且索引跳躍掃描的成本低於其餘掃描方式的成本時,Oracle就會使用該方式掃描組合索引;
四、 Oracle優化器有時會作出錯誤的選擇,由於它再「聰明」,也不如咱們SQL語句編寫人員更清楚表中數據的分佈,在這種狀況下,經過使用提示(hint),咱們能夠幫助Oracle優化器做出更好的選擇。 優化
QL> create index idx_test on yangtest (object_type,object_name); 索引已建立。 SQL> exec dbms_stats.gather_table_stats(user,'YANGTEST',cascade=>true); PL/SQL 過程已成功完成。 已用時間: 00: 00: 20.78 SQL> select object_type,count(*) from yangtest group by object_type order by 2; OBJECT_TYPE COUNT(*) ------------------- ---------- EDITION 1 RULE 1 MATERIALIZED VIEW 1 SCHEDULE 2 WINDOW GROUP 4 DIRECTORY 5 UNDEFINED 6 LOB PARTITION 7 RESOURCE PLAN 7 CONTEXT 7 WINDOW 9 CLUSTER 10 JOB 11 EVALUATION CONTEXT 11 INDEXTYPE 11 JOB CLASS 13 CONSUMER GROUP 14 RULE SET 17 PROGRAM 18 QUEUE 33 OPERATOR 57 XML SCHEMA 91 TABLE PARTITION 108 INDEX PARTITION 128 PROCEDURE 131 LIBRARY 179 TYPE BODY 224 SEQUENCE 227 FUNCTION 296 JAVA DATA 324 TRIGGER 482 LOB 760 JAVA RESOURCE 833 PACKAGE BODY 1206 PACKAGE 1267 TABLE 2543 TYPE 2616 INDEX 3194 VIEW 4749 JAVA CLASS 22103 SYNONYM 26670 已選擇41行。 已用時間: 00: 00: 00.09
當使用基於規則的優化器(RBO)時,只有當組合索引的前導列出如今SQL語句的where子句中時,纔會使用到該索引;code
SQL> set autot trace SQL> select /*+ rule */ * from yangtest where object_type='JOB'; 已選擇11行。 已用時間: 00: 00: 00.07 執行計劃 ---------------------------------------------------------- Plan hash value: 2067289980 ------------------------------------------------ | Id | Operation | Name | ------------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS BY INDEX ROWID| YANGTEST | |* 2 | INDEX RANGE SCAN | IDX_TEST | ------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_TYPE"='JOB') Note ----- - rule based optimizer used (consider using cbo) 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 13 consistent gets 0 physical reads 0 redo size 2310 bytes sent via SQL*Net to client 416 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 11 rows processed
而讓CBO本身選擇時,卻選擇了TFS,從信息統計裏面能夠看出consistent gets 是前者的100倍。CBO 也不必定很聰明。orm
SQL> select * from yangtest where object_type='JOB'; 已選擇11行。 已用時間: 00: 00: 00.03 執行計劃 ---------------------------------------------------------- Plan hash value: 911235955 ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1668 | 164K| 275 (1)| 00:00:04 | |* 1 | TABLE ACCESS FULL| YANGTEST | 1668 | 164K| 275 (1)| 00:00:04 | ------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_TYPE"='JOB') 統計信息 ---------------------------------------------------------- 264 recursive calls 0 db block gets 1050 consistent gets 0 physical reads 0 redo size 2006 bytes sent via SQL*Net to client 416 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 11 rows processed
因爲使用了組合索引的前導列而且訪問了表中的少許記錄,Oracle明智地選擇了索引掃描。索引
那麼,若是咱們訪問表中的大量數據時,Oracle會選擇什麼樣的訪問路徑呢?ip
看下面的測試:get
SQL> select * from yangtest where object_type='SYNONYM'; 已選擇26670行。 已用時間: 00: 00: 01.42 執行計劃 ---------------------------------------------------------- Plan hash value: 911235955 ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1668 | 164K| 275 (1)| 00:00:04 | |* 1 | TABLE ACCESS FULL| YANGTEST | 1668 | 164K| 275 (1)| 00:00:04 | ------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_TYPE"='SYNONYM') 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 2769 consistent gets 0 physical reads 0 redo size 1228701 bytes sent via SQL*Net to client 19963 bytes received via SQL*Net from client 1779 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 26670 rows processed
測試一下是使用RULE 的優化器。hash
SQL> select /*+ rule */ * from yangtest where object_type='SYNONYM'; 已選擇26670行。 已用時間: 00: 00: 01.56 執行計劃 ---------------------------------------------------------- Plan hash value: 2067289980 ------------------------------------------------ | Id | Operation | Name | ------------------------------------------------ | 0 | SELECT STATEMENT | | | 1 | TABLE ACCESS BY INDEX ROWID| YANGTEST | |* 2 | INDEX RANGE SCAN | IDX_TEST | ------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_TYPE"='SYNONYM') Note ----- - rule based optimizer used (consider using cbo) 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 23543 consistent gets --明顯比cbo的執行計劃的多10倍。 0 physical reads 0 redo size 3235078 bytes sent via SQL*Net to client 19963 bytes received via SQL*Net from client 1779 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 26670 rows processed
從以上結果能夠看出,在訪問大量數據的狀況下,使用索引確實會致使更高的執行成本,這從statistics部分的邏輯讀取數(consistent gets)就能夠看出,使用索引致使的邏輯讀取數是不使用索引致使的邏輯讀的10倍還多。所以,Oracle明智地選擇了全表掃描而不是索引掃描。io
下面,讓咱們來看看where子句中沒有索引前導列的狀況:
SQL> select * from yangtest where object_name ='EMP'; 已用時間: 00: 00: 00.01 執行計劃 ---------------------------------------------------------- Plan hash value: 4208055961 ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 202 | 45 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| YANGTEST | 2 | 202 | 45 (0)| 00:00:01 | |* 2 | INDEX SKIP SCAN | IDX_TEST | 2 | | 43 (0)| 00:00:01 | ---------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_NAME"='EMP') filter("OBJECT_NAME"='EMP') 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 35 consistent gets 1 physical reads 0 redo size 1337 bytes sent via SQL*Net to client 416 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed SQL> select * from yangtest where object_name ='YANGTEST'; 未選定行 已用時間: 00: 00: 00.01 執行計劃 ---------------------------------------------------------- Plan hash value: 4208055961 ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 2 | 202 | 45 (0)| 00:00:01 | | 1 | TABLE ACCESS BY INDEX ROWID| YANGTEST | 2 | 202 | 45 (0)| 00:00:01 | |* 2 | INDEX SKIP SCAN | IDX_TEST | 2 | | 43 (0)| 00:00:01 | ---------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_NAME"='YANGTEST') filter("OBJECT_NAME"='YANGTEST') 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 27 consistent gets 0 physical reads 0 redo size 1124 bytes sent via SQL*Net to client 405 bytes received via SQL*Net from client 1 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 0 rows processed
沒有使用前導列,Oracle正確地選擇了索引跳躍掃描。咱們再來看看若是不使用索引跳躍掃描,該語句的成本:
SQL> select /*+ NO_INDEX(YANGTEST,IDX_TEST)*/ * from yangtest where object_name ='DEPT'; 已用時間: 00: 00: 00.01 執行計劃 ---------------------------------------------------------- Plan hash value: 911235955 ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 2 | 202 | 275 (1)| 00:00:04 | |* 1 | TABLE ACCESS FULL| YANGTEST | 2 | 202 | 275 (1)| 00:00:04 | ------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_NAME"='DEPT') 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 1011 consistent gets --是使用索引跳躍掃描的50倍左右 0 physical reads 0 redo size 1335 bytes sent via SQL*Net to client 416 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed SQL> select * from yangtest where object_name like 'T%'; 已選擇136行。 已用時間: 00: 00: 00.04 執行計劃 ---------------------------------------------------------- Plan hash value: 911235955 ------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 925 | 93425 | 275 (1)| 00:00:04 | |* 1 | TABLE ACCESS FULL| YANGTEST | 925 | 93425 | 275 (1)| 00:00:04 | ------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_NAME" LIKE 'T%') 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 1020 consistent gets 0 physical reads 0 redo size 8900 bytes sent via SQL*Net to client 515 bytes received via SQL*Net from client 11 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 136 rows processed
此次只選擇了136條數據,跟表YANGTEST中總的數據量29489條相比,顯然只是很小的一部分,可是Oracle仍是選擇了全表掃描,有1020 個邏輯讀。
這種狀況下,若是咱們強制使用索引結果以下
SQL> select /*+ INDEX(YANGTEST,IDX_TEST)*/ * from yangtest where object_name like 'T%'; 已選擇136行。 已用時間: 00: 00: 00.06 執行計劃 ---------------------------------------------------------- Plan hash value: 972231820 ---------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 925 | 93425 | 1084 (1)| 00:00:14 | | 1 | TABLE ACCESS BY INDEX ROWID| YANGTEST | 925 | 93425 | 1084 (1)| 00:00:14 | |* 2 | INDEX FULL SCAN | IDX_TEST | 925 | | 424 (1)| 00:00:06 | ---------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_NAME" LIKE 'T%') filter("OBJECT_NAME" LIKE 'T%') 統計信息 ---------------------------------------------------------- 1 recursive calls 0 db block gets 537 consistent gets 0 physical reads 0 redo size 14700 bytes sent via SQL*Net to client 515 bytes received via SQL*Net from client 11 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 136 rows processed
經過添加提示(hint),咱們強制Oracle使用了索引掃描(index full scan),執行了335個邏輯讀,比使用全表掃描的時候少了一些。
因而可知,Oracle優化器有時會作出錯誤的選擇,由於它再「聰明」,也不如咱們SQL語句編寫人員更清楚表中數據的分佈,在這種狀況下,經過使用提示(hint),咱們能夠幫助Oracle優化器做出更好的選擇。