系統從 SQL Server 移植至 Oracle 後,某「查找無子記錄的父記錄」查詢從 1 秒飆升至 10 秒以上。本文透過實測對比,揭示 Oracle 中 NOT IN 子查詢的性能陷阱,並提供 MINUS 集合運算優化方案。
父表 PARENT、子表 CHILDREN,需查詢「沒有孩子的父親」。子表資料量 50 萬筆。
-- 標準寫法:NOT IN 子查詢 (耗時 10 秒+)
SELECT * FROM PARENT
WHERE ID NOT IN (SELECT PID FROM CHILDREN);
-- 改寫後:MINUS 差集運算 (耗時 < 1 秒)
SELECT * FROM PARENT
WHERE ID IN (
SELECT ID FROM PARENT
MINUS
SELECT PID FROM CHILDREN
);
| 資料量 | NOT IN 寫法 | MINUS 寫法 | 備註 |
|---|---|---|---|
| 子表 50 萬筆 | > 10 秒 | < 1 秒 | 差異極大 |
| 子表少量資料 | 正常 | 正常 | 兩者皆快 |
NULL,NOT IN 整體結果為空集,Oracle 必須逐行檢查空值,無法使用索引。FILTER 操作,對外部表每筆資料掃描內部表,呈現 Nested Loop 特性,複雜度 O(M×N)。HASH JOIN ANTI,但受限於統計資訊、空值、相關子查詢等條件常轉換失敗。MINUS 觸發 HASH SET OPERATION,雙方均可全表掃描並建立雜湊表,複雜度 O(M+N)。-- 1. NOT EXISTS (推薦,語意清晰,易產生 HASH JOIN ANTI)
SELECT * FROM PARENT P
WHERE NOT EXISTS (SELECT 1 FROM CHILDREN C WHERE C.PID = P.ID);
-- 2. LEFT JOIN + IS NULL (傳統寫法)
SELECT P.* FROM PARENT P
LEFT JOIN CHILDREN C ON P.ID = C.PID
WHERE C.PID IS NULL;
-- 3. 外部連接 + 過濾 (舊版 Oracle 常用)
SELECT P.* FROM PARENT P, CHILDREN C
WHERE P.ID = C.PID(+) AND C.PID IS NULL;
MINUS/INTERSECT 是強大工具,善用集合思維替代逐行比對。DBMS_STATS.GATHER_TABLE_STATS 讓優化器選擇最佳計畫。Oracle 與 SQL Server 優化器邏輯差異巨大,移植專案必須重新審視 SQL 寫法。本案例證明:集合運算 (MINUS) 在大數據量差集查詢中遠優於 NOT IN,開發者應建立集合思維,善用 Oracle 強大的集合運算優化能力。