| 場景 | 推薦 | 理由 |
|---|---|---|
| 外表大、內表小 | IN |
內表資料少,記憶體緩存遍歷極快 |
| 外表小、內表大 | EXISTS |
只掃描外表,內表走索引求值,避免大表全掃 |
| 兩表相近 | 均可 | 優化器常自動重寫,效能差異微小 |
IN:記憶體集合比對SELECT * FROM A WHERE id IN (SELECT id FROM B);
邏輯等價:
List B_ids = SELECT id FROM B; -- 一次性查出並緩存
FOR each row_a IN A:
IF row_a.id IN B_ids: OUTPUT row_a;
特點:子查詢只執行一次,結果集載入記憶體。若 B 表百萬行,記憶體壓力大、緩存構建慢。
EXISTS:關聯子查詢逐行求值SELECT * FROM A WHERE EXISTS (SELECT 1 FROM B WHERE A.id = B.id);
邏輯等價:
FOR each row_a IN A:
IF (SELECT 1 FROM B WHERE B.id = row_a.id LIMIT 1) IS NOT EMPTY:
OUTPUT row_a;
特點:子查詢執行 A 表行數次,但可利用 B 表索引(WHERE B.id = ?),每次只需索引查找,不掃全表。不緩存結果集,只關心「是否存在」。
| 場景 | A 表行數 | B 表行數 | IN 遍歷次數 | EXISTS 執行次數 | 勝出 |
|---|---|---|---|---|---|
| 1 | 10,000 | 1,000,000 | 100 億 | 10,000 | EXISTS |
| 2 | 10,000 | 100 | 100 萬 | 10,000 | IN |
INSERT INTO A (name, age)
SELECT name, age FROM B
WHERE NOT EXISTS (SELECT 1 FROM A WHERE A.id = B.id);
NOT EXISTS 比 NOT IN 更安全:NOT IN 若子查詢含 NULL 會導致整體判斷為 UNKNOWN,無資料寫入。
IN/EXISTS 互轉為 SEMI JOIN,實際執行計劃可能相同EXISTS 依賴內表連接鍵索引;IN 依賴內表小到能放入記憶體