文章出處:http://space.itpub.net/17203031/viewspace-692364 感謝作者的分享
索引是我們經常選擇的數據表檢索優化方案之一。其中,複合索引是我們經常選擇的策略。那麼,構建索引列的順序上,有何種差異和需要注意的方面呢?下面我們通過實驗來進行說明。
實驗環境說明
準備數據表和實驗環境。索引列的差異,主要體現在選擇性上,我們通過構建不同選擇性的列來進行試驗。
SQL> conn scott/tiger@orcl;
Connected to Oracle Database10gEnterpriseEdition Release10.2.0.1.0
Connected as scott
SQL> create table t as select owner, object_name from dba_objects;
Table created
SQL> select count(distinct owner), count(distinct object_name) from t;
COUNT(DISTINCTOWNER) COUNT(DISTINCTOBJECT_NAME)
-------------------- --------------------------
30 30716
可以看出,在數據表T上不同列具有很大的選擇性差異。
構建方案1——低選擇性列爲前導列
首先我們選擇低選擇性列owner作爲索引列的前導列。
SQL> create index idx_t_cmp1 on t(owner,object_name);
Index created
SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);
PL/SQL procedure successfully completed
首先來觀察一下索引物理體積問題。
SQL> col segment_name for a15;
SQL> select segment_name, bytes, blocks, extents from user_segments where segment_name='IDX_T_CMP1';
SEGMENT_NAME BYTES BLOCKS EXTENTS
--------------- ---------- ---------- ----------
IDX_T_CMP1 3145728 384 18
佔有空間上爲384個Oracle塊,分佈在18個分區上。
搜索場景執行計劃研究。
場景1:where條件中包括所有索引列;
SQL> explain plan for select * from t where wner='SCOTT' and object_name='T';
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1474811917
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 29 | 1 (0)| 00:00:01 |
|*1 | INDEXRANGESCAN| IDX_T_CMP1 | 1 | 29 | 1 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OWNER"='SCOTT' AND "OBJECT_NAME"='T')
13 rows selected
當所有列均出現在where條件中時,Oracle選擇的執行計劃中進行“INDEX RANGE SCAN”操作。Oracle索引結構中,葉節點排列的就是索引列排序的結果。進行的“INDEX RANGE SCAN”操作,就是首先根據條件,從根root節點位置向下定位,經過分支節點之後,定位到第一個符合條件索引列鍵值的葉節點。之後順序掃描葉子節點,獲取到符合where條件(或者部分where符合條件)的數據表列rowid值。
Index Range Scan操作是Oracle進行索引操作最常見的形式。
場景2:where中包括低選擇性列
SQL> explain plan for select * from t where wner='SCOTT';
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 1474811917
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1901 | 55129 | 12 (0)| 00:00:01 |
|* 1 |INDEXRANGESCAN| IDX_T_CMP1 | 1901 | 55129 | 12 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OWNER"='SCOTT')
13 rows selected
當條件中只有低選擇性列的時候,Oracle同樣可以通過INDEX RANGE SCAN來獲取rowid值。雖然並不能完全發揮出索引的全部列優勢,但是Oracle通過Cost試算,通常可以判斷出只掃描部分索引樹,也是能帶來較好的搜索性能的。
場景2:where條件中帶高選擇性列
SQL> explain plan for select * from t where object_name='T';
Explained
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------
Plan hash value: 3522166362
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 58 | 28 (0)| 00:00:01 |
|* 1 |INDEX SKIP SCAN| IDX_T_CMP1 | 2 | 58 | 28 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("OBJECT_NAME"='T')
filter("OBJECT_NAME"='T')
14 rows selected
此處,where條件中沒有出現索引前導列owner,而是出現了選擇性較強的object_name列。此時,我們發現Oracle選擇利用索引進行了“INDEX SKIP SCAN”操作。首先,我們從CBO的角度看,進行該操作所消耗的成本必然要比進行FTS(全表掃描)的成本要低。
INDEX SKIP SCAN是Oracle 9i中引入的一種執行計劃操作。故名思意,就是對索引葉節點進行“跳躍”式的搜索。在這個問題上,網絡中一些資料認爲:
Oracle中的複合索引順序不同,對索引構建結構上有很大的影響。首先,Oracle依據前導列的取值將索引樹劃分爲多個子索引結構。如果前導列取值較多,也就意味着子樹多。在進行帶前導列搜索時,Oracle首先依據前導列確定子索引樹,之後進行各種的Index Range Scan。此時的Range Scan是進行索引葉子節點的掃描。
無論這種理解是否正確,有一點可以肯定。當where條件中不包括前導列的時候,對葉子節點進行Range Scan應該是不可以的。因爲Range Scan保證的順序是前導列+後導列的順序。Skip Scan應該進行的是在葉子節點上,根據不同的前導列形成子索引樹,葉節點分別進行Scan操作。
筆者以爲:skip scan是Oracle針對特定條件上索引結構,所提供的一種備選搜索操作。Skip scan的使用不是規則,而是成本估算。Index Skip Scan是Oracle提供的一種執行計劃操作,可以應用在執行計劃的生成中。簡單的說,就是Oracle將SQL描述語句轉化爲可執行操作序列(執行計劃)過程中一個操作選擇。