為了直觀展示 Oracle 優化器在不同條件下選擇的數據訪問方式,我們首先構建一個測試表 TEST,並建立索引、收集統計信息:
CREATE TABLE test AS SELECT * FROM dba_objects WHERE 0=1;
CREATE INDEX ind_test_id ON test(object_id);
INSERT INTO test SELECT * FROM dba_objects WHERE object_id IS NOT NULL AND object_id > 10000 ORDER BY object_id DESC;
ANALYZE TABLE test COMPUTE STATISTICS FOR TABLE FOR ALL COLUMNS FOR ALL INDEXES;
當查詢無 WHERE 條件,且欄位允許 NULL 時,優化器傾向於全表掃描。
SQL> SET AUTOTRACE TRACE
SQL> SELECT object_id FROM test;
Execution Plan
----------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 1 | TABLE ACCESS FULL | TEST | 58650 | 229K| 239 (1)| 00:00:03 |
將 object_id 修改為 NOT NULL 後,同樣的查詢改用索引快速全掃描,成本大幅降低。
SQL> ALTER TABLE test MODIFY (object_id NOT NULL);
SQL> SELECT object_id FROM test;
Execution Plan
----------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 1 | INDEX FAST FULL SCAN | IND_TEST_ID| 58650 | 229K| 66 (0)| 00:00:01 |
原理:索引快速全掃描以多區塊讀取方式掃描索引葉子塊,不遵循索引順序,適用於索引欄位為 NOT NULL 且需讀取大部分數據的場景。
使用 Hint 強制走索引全掃描,會按照索引順序單塊讀取,成本反而高於全表掃描。
SQL> SELECT /*+ INDEX(test ind_test_id) */ object_id FROM test;
Execution Plan
----------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 1 | INDEX FULL SCAN | IND_TEST_ID| 58650 | 229K| 240 (1)| 00:00:03 |
加上範圍條件 WHERE object_id < 68926 時,優化器選擇索引範圍掃描。
SQL> SELECT /*+ INDEX(test ind_test_id) */ object_id FROM test WHERE object_id < 68926;
Execution Plan
----------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
| 1 | INDEX RANGE SCAN | IND_TEST_ID| 57903 | 226K| 237 (1)| 00:00:03 |
當使用 ROWNUM < 11 限制輸出行數時,訪問路徑會因 Hint 與條件順序而改變:
INDEX FAST FULL SCAN,返回的是物理順序靠後的數據(因表建立時按 DESC 排序)。-- 無 Hint,INDEX FAST FULL SCAN
SELECT object_id FROM test WHERE rownum < 11;
-- 結果: 68917, 68918... (較大值)
-- 有 Hint,INDEX FULL SCAN
SELECT /*+ INDEX(test ind_test_id) */ object_id FROM test WHERE rownum < 11;
-- 結果: 10001, 10002... (較小值)