SQL 效能調優無捷徑,但掌握核心原則即能解決 80% 的效能問題。本文整理 8 大經典調優法則,附反面教材與正確寫法對照,助您寫出高效 SQL。
原則:子查詢若只需判斷「存在與否」,EXISTS 只需掃描到第一筆符合即返回,IN 需將子查詢結果全數物化再比對。
-- 低效:IN 需完整掃描 DEPT
SELECT * FROM EMP WHERE DEPTNO IN (SELECT DEPTNO FROM DEPT WHERE LOC = 'MELB');
-- 高效:EXISTS 遇第一筆即停止
SELECT * FROM EMP e WHERE EXISTS (SELECT 'X' FROM DEPT d WHERE d.DEPTNO = e.DEPTNO AND d.LOC = 'MELB');
原則:HAVING 在 GROUP BY 完成後才過濾,需先聚合全量資料;WHERE 可在聚合前過濾,減少參與排序/聚合的資料量。
-- 低效:先 GROUP BY 全表再 HAVING
SELECT REGION, AVG(LOG_SIZE) FROM LOCATION GROUP BY REGION HAVING REGION NOT IN ('SYDNEY','PERTH');
-- 高效:WHERE 先過濾再 GROUP BY
SELECT REGION, AVG(LOG_SIZE) FROM LOCATION WHERE REGION NOT IN ('SYDNEY','PERTH') GROUP BY REGION;
原則:多表 JOIN 時加上別名並前綴欄位(e.EMPNO),減少解析器解析歧義的時間,也能避免同名欄位報錯。
原則:一對多關聯查詢時,DISTINCT 需排序去重;EXISTS 只需確認存在即可,避免排序開銷。
-- 低效:DISTINCT 需排序
SELECT DISTINCT d.DEPT_NO, d.DEPT_NAME FROM DEPT d, EMP e WHERE d.DEPT_NO = e.DEPT_NO;
-- 高效:EXISTS 無排序
SELECT d.DEPT_NO, d.DEPT_NAME FROM DEPT d WHERE EXISTS (SELECT 'X' FROM EMP e WHERE e.DEPT_NO = d.DEPT_NO);
原則:OR 導致全表掃描;UNION 可分別走各索引再合併(前提:所有欄位皆有索引)。
-- 高效:UNION 走兩個索引
SELECT LOC_ID, LOC_DESC, REGION FROM LOCATION WHERE LOC_ID = 10
UNION
SELECT LOC_ID, LOC_DESC, REGION FROM LOCATION WHERE REGION = 'MELBOURNE';
-- 低效:OR 導致全表掃描
SELECT LOC_ID, LOC_DESC, REGION FROM LOCATION WHERE LOC_ID = 10 OR REGION = 'MELBOURNE';
若堅持用 OR,將選擇率最高(回傳行數最少)的索引欄位寫在最前面。
-- 低效
SELECT ... FROM LOCATION WHERE LOC_ID = 10 OR LOC_ID = 20 OR LOC_ID = 30;
-- 高效:IN 內部優化為排序後二分搜尋
SELECT ... FROM LOCATION WHERE LOC_ID IN (10, 20, 30);
原則:Oracle 解析器採「自下而上」順序評估 WHERE 條件(舊版 RBO 模式),將表連接條件寫在前面,高選擇性過濾條件寫在最後。
-- 低效(156.3 秒):高過濾條件寫在前
SELECT ... FROM EMP e WHERE SAL > 50000 AND JOB = 'MANAGER' AND 25 < (SELECT COUNT(*) FROM EMP WHERE MGR = e.EMPNO);
-- 高效(10.6 秒):子查詢高過濾條件前置
SELECT ... FROM EMP e WHERE 25 < (SELECT COUNT(*) FROM EMP WHERE MGR = e.EMPNO) AND SAL > 50000 AND JOB = 'MANAGER';
結論:COUNT(*) ≈ COUNT(1)(現代版本已優化相同),但 COUNT(索引欄位) 若該索引為 NOT NULL 且較窄,可走 Index Fast Full Scan 最快。
-- 最快:走 EMPNO 索引
SELECT COUNT(EMPNO) FROM EMP;
EXISTSWHERE 非 HAVINGEXISTSOR 改 UNIONOR 改 IN