索引是資料庫查詢加速的核心杠桿,但**建立 ≠ 生效**。本文從 B+ 樹儲存機制出發,系統講解叢集/非叢集/複合/唯一索引特性,列舉 4 大類導致索引失效的 SQL 寫法,並以 EXPLAIN 實戰演示如何驗證與修正索引使用情況。
BETWEEN, >, <) 與有序輸出 (ORDER BY)| 類型 | 語法 (MySQL/PostgreSQL/Oracle 通用) | 核心特性 |
|---|---|---|
| 單列索引 | CREATE INDEX idx_col ON tbl(col); | 單一欄位查詢加速 |
| 複合索引 | CREATE INDEX idx_a_b ON tbl(a, b); | **最左前綴原則**:查詢必須包含前導欄位 a 才能用到 |
| 唯一索引 | CREATE UNIQUE INDEX idx_uk ON tbl(uk_col); | 等同 UNIQUE CONSTRAINT,禁止重複值,含 NULL 視為重複 (Oracle 除外) |
| 叢集索引 | 主鍵自動建立 / CREATE CLUSTERED INDEX ... (SQL Server) | 資料物理順序 = 索引順序,每表僅一個 |
-- ❌ 失效:前導萬用字元
SELECT * FROM emp WHERE ename LIKE '%SMITH';
SELECT * FROM emp WHERE ename LIKE '%MI%';
-- ✅ 生效:前綴匹配
SELECT * FROM emp WHERE ename LIKE 'SMI%';
-- 💡 對策:全文檢索 (Oracle Text / PostgreSQL GIN / MySQL FULLTEXT) 或反轉索引 (Oracle REVERSE KEY)
-- ❌ 失效:否定、不等於、IS [NOT] NULL
SELECT * FROM emp WHERE deptno <> 10;
SELECT * FROM emp WHERE comm IS NOT NULL;
-- ✅ 改寫:
SELECT * FROM emp WHERE deptno IN (20,30); -- 已知值集合
SELECT * FROM emp WHERE comm > 0; -- 業務語義允許時
-- 💡 部分索引 (PostgreSQL/Oracle 12c+):
CREATE INDEX idx_comm_notnull ON emp(comm) WHERE comm IS NOT NULL;
-- ❌ 失效:函數、運算、隱含轉換
SELECT * FROM emp WHERE YEAR(hiredate) = 1981;
SELECT * FROM emp WHERE sal + comm > 5000;
SELECT * FROM emp WHERE empno = '7369'; -- 字串比較數字 → 隱含 TO_NUMBER(empno)
-- ✅ 改寫:將運算移至常數側
SELECT * FROM emp WHERE hiredate >= '1981-01-01' AND hiredate < '1982-01-01';
SELECT * FROM emp WHERE sal > 5000 - comm;
SELECT * FROM emp WHERE empno = 7369;
-- 💡 Function-Based Index (Oracle/PostgreSQL/MySQL 8.0+):
CREATE INDEX idx_hiredate_year ON emp(EXTRACT(YEAR FROM hiredate));
-- 或虛擬欄位 + 索引
CREATE INDEX idx_dept_job ON emp(deptno, job);
-- ✅ 用到索引
SELECT * FROM emp WHERE deptno = 10;
SELECT * FROM emp WHERE deptno = 10 AND job = 'CLERK';
-- ❌ 失效:缺少前導欄位
SELECT * FROM emp WHERE job = 'CLERK';
-- ❌ 失效:OR 條件跨欄位 (Index Merge 極罕見)
SELECT * FROM emp WHERE deptno = 10 OR job = 'CLERK';
-- 💡 對策:建立單獨索引 idx_job,或改寫 UNION ALL
SELECT * FROM emp WHERE deptno = 10
UNION ALL
SELECT * FROM emp WHERE job = 'CLERK' AND deptno <> 10;
-- MySQL / PostgreSQL
EXPLAIN SELECT * FROM emp WHERE deptno = 10;
-- Oracle
EXPLAIN PLAN FOR SELECT * FROM emp WHERE deptno = 10;
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY());
| 欄位 | 關注重點 |
|---|---|
type (MySQL) / Operation (Oracle) | ref/range/eq_ref = 索引生效;ALL/index = 全表/全索引掃描 ❌ |
key / Object Name | 實際使用的索引名稱 |
rows / Cardinality | 預估掃描行數,**越小越好** |
Extra / Filter Predicates | Using where = 索引後再過濾;Using index = 覆蓋索引 ✅;Using filesort = 需排序 ⚠️ |
rows、cost、Execution Time,量化收益EXPLAIN 確認核心 SQL 走預期索引?MONITORING USAGE 追蹤實際使用情況?索引使用不是「建好就對」,而是 **「SQL 寫法匹配索引結構 → 優化器識別 → 執行計畫驗證 → 持續監控」** 的閉環工程。養成 **寫 SQL 即跑 EXPLAIN** 的肌肉記憶,是高效開發者的分水嶺。