子查詢是 SQL 編寫直觀但效能隱患最大的結構之一。MySQL 優化器對子查詢有多層次優化策略,理解其原理能指導寫出高效 SQL。
| 維度 | 分類 | 特徵 |
|---|---|---|
| 相關性 | 相關子查詢 | 引用外層查詢欄位,需隨外層重複執行 |
| 非相關子查詢 | 獨立可單獨求解,可下推/物化 | |
| 謂詞 | IN / ANY / SOME / ALL | 集合成員資格測試 |
| EXISTS / NOT EXISTS | 半連接語義,存在性測試 | |
| 比較運算子 (=, >, <...) | 標量子查詢,要求單值回傳 | |
| 複雜度 | SPJ 子查詢 | Select-Project-Join,無聚合/排序 |
| GROUP BY 子查詢 | 含聚合/分組 | |
| 其他 (LIMIT/UNION/ORDER BY) | 非標準關係代數 | |
| 結果集 | 標量 | 單值 |
| 單行單列 | 0 或 1 行 | |
| 多行單列 | 多行 1 列 | |
| 表子查詢 | 多行多列 (FROM 子句) |
語義等價的多個子查詢合併為一個,減少掃描/連接次數。
-- 原始:兩個 EXISTS SELECT * FROM t1 WHERE a1 < 10 AND ( EXISTS (SELECT a2 FROM t2 WHERE t2.a2 < 5 AND t2.b2 = 1) OR EXISTS (SELECT a2 FROM t2 WHERE t2.a2 < 5 AND t2.b2 = 2) ); -- 優化:合併條件 SELECT * FROM t1 WHERE a1 < 10 AND ( EXISTS (SELECT a2 FROM t2 WHERE t2.a2 < 5 AND (t2.b2 = 1 OR t2.b2 = 2)) );
將子查詢「拉平」為外層查詢的連接 (JOIN),讓優化器統一考慮連接順序、存取路徑。
-- 原始:FROM 子查詢 SELECT * FROM t1, (SELECT * FROM t2 WHERE a2 > 10) v_t2 WHERE t1.a1 < 10 AND v_t2.a2 < 20; -- 展開後:直接連接 SELECT * FROM t1, t2 WHERE t1.a1 < 10 AND t2.a2 < 20 AND t2.a2 > 10;
展開必要條件:
標量聚合子查詢 (如 SELECT AVG(...)) 可下推或轉為連接。
-- 原始 SELECT * FROM t1 WHERE t1.a1 > (SELECT AVG(t2.a2) FROM t2); -- 優化:物化聚合結果再連接 (MySQL 5.7+ 衍生表合併) SELECT t1.* FROM t1 JOIN (SELECT AVG(a2) AS avg_a2 FROM t2) dt ON t1.a1 > dt.avg_a2;
針對 IN / EXISTS 子查詢,MySQL 採用半連接 避免重複行:
可透過 optimizer_switch 控制:
SET optimizer_switch='semijoin=on,materialization=on,firstmatch=on,loosescan=on';
| 原始模式 | 優化改寫 | 原理 |
|---|---|---|
| WHERE col IN (SELECT ...) | JOIN + DISTINCT / EXISTS | 半連接 / 物化 |
| WHERE col = (SELECT ...) | JOIN 衍生表 | 展開 / 聚合消除 |
| FROM (SELECT ...) v | 直接 JOIN 基表 | 展開 / 衍生表合併 |
| SELECT (SELECT ...) FROM t | LEFT JOIN 衍生表 | 標量子查詢外提 |
EXPLAIN SELECT ...; -- 關注: -- select_type: DEPENDENT SUBQUERY → SIMPLE / PRIMARY (展開成功) -- type: ALL → ref/eq_ref (索引生效) -- Extra: Using temporary; Using filesort → 消失
optimizer_trace、半連接 策略。建議:MySQL 8.0+ 直接使用 WITH 重寫複雜子查詢,可讀性與效能雙贏。