優化器基於成本模型 (CBO) 決定訪問路徑。即使存在完美索引,特定 SQL 寫法會導致優化器判定「全表掃描成本更低」或「無法使用索引」。本文剖析三大核心陷阱,提供改寫模式與驗證方法。
複合索引 IDX_TBL(F1, F2, F3) 的 B-Tree 結構按 F1 → F2 → F3 排序。WHERE 條件必須包含 最左列 F1 才能觸發索引範圍掃描。
| WHERE 條件 | 索引使用 | 說明 |
|---|---|---|
F1 = :v1 | ✅ 範圍掃描 | 最左列精確匹配 |
F1 = :v1 AND F2 = :v2 | ✅ 範圍掃描 | 最左兩列精確匹配 |
F2 = :v2 | ❌ 全表掃描 | 缺失最左列 F1,索引不可用 |
F1 = :v1 AND F3 = :v3 | ⚠️ 部分使用 | 僅用 F1 範圍掃描,F3 為過濾條件 |
對策:調整索引列順序、創建獨立索引、使用 Index Skip Scan (10g+,基數低時)。
任何形式的列操作會導致優化器無法利用索引順序特性:
-- 典型失效寫法
WHERE TO_DATE(update_date) >= TO_DATE('2001-07-18','YYYY-MM-DD') -- 函數
WHERE UPPER(username) = 'ADMIN' -- 函數
WHERE salary + bonus > 10000 -- 表達式
WHERE SUBSTR(id,1,3) = '001' -- 函數
WHERE user_id = '12345' -- user_id 是 NUMBER,發生隱式 TO_NUMBER 轉換
改寫模式:
WHERE update_date >= TO_DATE('2001-07-18','YYYY-MM-DD')CREATE INDEX idx_upper_user ON users(UPPER(username))ADD (upper_user GENERATED ALWAYS AS (UPPER(username)) VIRTUAL)WHERE user_id = TO_NUMBER('12345') 或綁定變量正確類型以下操作符會 顯式阻止 索引範圍掃描 (除非配合特殊索引/提示):
| 操作符/模式 | 失效原因 | 替代方案 |
|---|---|---|
col IS NULL / IS NOT NULL | B-Tree 不存儲全 NULL 鍵值 | 位圖索引、函數索引 NVL(col,' ')、分區剪枝 |
col != val / col <> val / NOT IN | 範圍不可確定,需掃描全表 | 改寫為 IN / UNION ALL、分區剪枝 |
col LIKE '%pattern%' | 前導通配符,無法範圍掃描 | Oracle Text / 反轉索引 / 後綴匹配改寫 |
NOT EXISTS (相關子查詢) | 通常轉為 Anti-Join,視統計信息決定 | 確保關聯列有索引、收集統計信息 |
col IN (subquery) | 某些版本/統計下退化為全表 | 改寫為 EXISTS / JOIN / UNION ALL |
col1 > col2 (同表兩列比較) | 無索引支撐 | 物化視圖 / 虛擬列 / 應用層過濾 |
EXPLAIN PLAN FOR SELECT ...;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
關注 Operation=TABLE ACCESS FULL vs INDEX RANGE SCAN、Cost、Cardinality。SELECT * FROM V$SQL_MONITOR WHERE SQL_TEXT LIKE '%...%';
查看 IO_INTERCONNECT_BYTES、ELAPSED_TIME、PLAN_LINE_ID 實際執行路徑。BIND_AWARE、BIND_SENSITIVE 標記對計劃穩定性的影響。DBMS_STATS.GATHER_TABLE_STATS(..., METHOD_OPT=>'FOR ALL COLUMNS SIZE AUTO')DBMS_STATS.GATHER_SCHEMA_STATS、DBMS_STATS.DELETE_TABLE_STATS 清理失效統計索引優化是統計信息、SQL 寫法、索引結構、優化器特性四方博弈的動態平衡。建立「寫 SQL → EXPLAIN → 基線測試 → 上線 → 監控 → 迭代」閉環,方能讓索引持續發揮價值。