子查詢是 SQL 編寫的強大工具,但也是性能殺手重災區。MySQL 優化器對不同類型子查詢的處理策略差異巨大:有的轉為半連接、有的物化、有的保留依賴子查詢。本文通過 EXPLAIN EXTENDED 實測,系統分析 IN、EXISTS、NOT IN、ALL/ANY/SOME 等類型的優化行為。
WHERE col IN (SELECT ...)、WHERE col = (SELECT ...)。SELECT * FROM (SELECT ...) AS t。WHERE EXISTS (SELECT 1 FROM ... WHERE outer.col = inner.col)。測試表:t1(id1, a1, b1), t2(id2, a2, b2),a2 有唯一索引。
EXPLAIN EXTENDED SELECT * FROM t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t1.a1 = t2.a2 AND t2.a2 > 10);
select_type=DEPENDENT SUBQUERY,外層 ALL,內層 ALL。EXISTS (SELECT ...) 結構。EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1 IN (SELECT a2 FROM t2 WHERE t2.a2 > 10);
select_type=MATERIALIZED (內層物化) + SIMPLE (外層) + Using join buffer (Block Nested Loop)。SELECT ... FROM t1 SEMI JOIN (t2) WHERE (t1.a1 = t2.a2 AND t2.a2 > 10)。EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1 IN (SELECT a2 FROM t2 WHERE t1.a1 = 10);
t1.a1,但優化器識別出可轉為常數條件,同樣執行半連接,select_type 變為 SIMPLE,無 DEPENDENT SUBQUERY。EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1 NOT IN (SELECT a2 FROM t2 WHERE t2.a2 > 10);
PRIMARY,內層 SUBQUERY,Extra: Using where。<in_optimizer>(... <materialize>(SELECT ...) ... <primary_index_lookup> ...)。-- > ALL 等價於 > (SELECT MAX(...))
EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1 > ALL (SELECT a2 FROM t2 WHERE t2.a2 > 10);
-- < ALL 等價於 < (SELECT MIN(...))
EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1 < ALL (SELECT a2 FROM t2 WHERE t2.a2 = 10);
-- = ALL 複雜,轉為 EXISTS 策略
EXPLAIN EXTENDED SELECT * FROM t1 WHERE t1.a1 = ALL (SELECT a2 FROM t2 WHERE t2.a2 = 10);
<= <max>(subquery) / >= <min>(subquery)。若子查詢列有唯一索引,**只需一次索引查找取極值**,極高效。select_type=SUBQUERY。NOT EXISTS 策略,select_type=DEPENDENT SUBQUERY。> (SELECT MIN(...)),最值優化,select_type=SUBQUERY。IN,**半連接優化**,物化 + Block Nested Loop,select_type=SIMPLE + MATERIALIZED。< (SELECT MAX(...)),最值優化。| 子查詢類型 | 優化策略 | 子查詢消除 | 關鍵條件 |
|---|---|---|---|
| EXISTS / NOT EXISTS | 無 (Dependent Subquery) | ❌ | — |
| IN (非相關/相關) | 半連接 + 物化 | ✅ | — |
| NOT IN (非相關) | 物化 + 索引查找 | ❌ | — |
| > ALL / < ALL | 最值優化 (MAX/MIN) | ✅ (單次執行) | 子查詢列有唯一索引 |
| = ALL | NOT EXISTS 策略 | ❌ | — |
| > SOME / > ANY | 最值優化 (MIN) | ✅ | 子查詢列有唯一索引 |
| = SOME / = ANY | 半連接 (同 IN) | ✅ | — |
| < SOME / < ANY | 最值優化 (MAX) | ✅ | 子查詢列有唯一索引 |
IN/EXISTS 往往可轉為 INNER JOIN / LEFT JOIN ... IS NULL,讓優化器有更多連接順序選擇。NOT IN (NULL, ...) 永遠為 FALSE/UNKNOWN,導致語義錯誤。用 NOT EXISTS 或 LEFT JOIN ... IS NULL。ALL/ANY/SOME 最值優化、半連接物化構建索引至關重要。select_type 是否為 DEPENDENT SUBQUERY (未優化)、MATERIALIZED (物化)、SIMPLE (半連接上拉)。derived_merge,FROM (SELECT ...) 可合併到外層,避免物化臨時表。用 optimizer_switch='derived_merge=on'。MySQL 優化器對子查詢的處理「參差不齊」:IN/ANY/SOME 半連接優化最成熟,EXISTS 系列仍保留依賴執行,NOT IN 僅物化不消除,ALL/ANY/SOME 比較操作符可觸發最值優化。理解這些規則,配合索引設計與改寫技巧,是寫出高性能 SQL 的關鍵。