JOIN 查詢是 SQL 效能調校的高頻戰場。不當的 LEFT JOIN、過度的關聯表、冗餘的子查詢常導致執行計劃退化為全表掃描與臨時表。本文透過 4 個真實優化案例,示範 LEFT JOIN → INNER JOIN、子查詢 → 關聯衍生表/標量子查詢、Left Join 消除規則 等核心技巧,並附上 MySQL 內核優化邏輯原理解析。
SELECT ...
FROM Resource_Resources A
LEFT JOIN Resource_Clients B ON A.ResourceId = B.ResourceId
LEFT JOIN Resource_Files C ON B.ClientId = C.ClientId
LEFT JOIN Resource_ClientsModels D ON B.ClientId = D.ClientId
LEFT JOIN Mobile_Models E ON D.ModelId = E.ModelId
LEFT JOIN Resource_Images F ON A.ResourceId = F.ResourceId
LEFT JOIN Resource_Tags G ON G.ResourceId = A.ResourceId
LEFT JOIN Website_Tags H ON G.TagId = H.TagId
LEFT JOIN Resource_Categories I ON A.CategoryId = I.CategoryId
WHERE A.Name LIKE ...
AND C.Extend LIKE ...
AND D.ModelId = ...
AND H.Name LIKE ...
AND I.Code LIKE ...
AND E.Name LIKE ...
資料量:A 表 2萬、B > A、C > B、H 表極大。
SELECT ...
FROM Resource_Resources A
INNER JOIN Resource_Clients B ON A.ResourceId = B.ResourceId
INNER JOIN Resource_Files C ON B.ClientId = C.ClientId
LEFT JOIN Resource_ClientsModels D ON B.ClientId = D.ResourceClientId
LEFT JOIN Resource_Tags G ON G.ResourceId = A.ResourceId
INNER JOIN Website_Tags H ON G.TagId = H.TagId
INNER JOIN Resource_Categories I ON A.CategoryId = I.CategoryId
WHERE A.Name LIKE ...
AND H.Name LIKE ...
C.Extend, D.ModelId, H.Name, I.Code, E.Name) 且非 IS NULL 判斷,等價於 INNER JOIN。MySQL 優化器自動將此類 LEFT JOIN 轉為 INNER JOIN。Mobile_Models E、Resource_Images F (未在 SELECT/WHERE 使用)。A.Name, H.Name),低選擇性條件下推至應用層或捨棄。INNER JOIN 逐步縮小結果集。SELECT M.clientid, M.CardFaceID,
N.NormalBalance, D.DateWorth, T.TimesWorth, B.BookingWorth, B.BookingTimesManyBalance
FROM (SELECT clientid, CardFaceID FROM cimain) M
LEFT JOIN (SELECT clientid, SUM(...) AS NormalBalance FROM ccNormal GROUP BY clientid) N ON M.clientid = N.clientid
LEFT JOIN (SELECT clientid, SUM(...) AS DateWorth FROM ccDate GROUP BY clientid) D ON M.clientid = D.clientid
LEFT JOIN (SELECT clientid, SUM(...) AS TimesWorth FROM ccTimes GROUP BY clientid) T ON M.clientid = T.clientid
LEFT JOIN (SELECT clientid, SUM(...) AS BookingWorth, SUM(TimesBalance) AS BookingTimesManyBalance FROM ccBooking GROUP BY clientid) B ON M.clientid = B.clientid
問題:每個衍生表需物化暫存表,再進行 4 次 JOIN,臨時表 I/O 開銷極大。
SELECT M.clientid, M.CardFaceID,
(SELECT SUM(CASE IFGive WHEN '是' THEN Balance*ItemZkl ELSE Balance END) FROM ccNormal WHERE clientid = M.clientid) AS NormalBalance,
(SELECT SUM(ConsumeBalance*ItemZkl) FROM ccDate WHERE clientid = M.clientid) AS DateWorth,
(SELECT SUM(AveragePrice*TimesBalance*ItemZKL) FROM ccTimes WHERE clientid = M.clientid) AS TimesWorth,
(SELECT SUM(PriceDiscount*TimesBalance) FROM ccBooking WHERE clientid = M.clientid) AS BookingWorth,
(SELECT SUM(TimesBalance) FROM ccBooking WHERE clientid = M.clientid) AS BookingTimesManyBalance
FROM cimain M
效果:cimain 為驅動表,每行透過 clientid 索引單點查找 4 張聚合表,無臨時表、無 JOIN、極低延遲。前提:4 張聚合表皆有 clientid 索引。
SELECT COUNT(DISTINCT T1.A1) + COUNT(DISTINCT T2.B1) + COUNT(DISTINCT T3.C1)
FROM T1
LEFT JOIN T2 ON T1.A1 = T2.A1
LEFT JOIN T3 ON T1.A1 = T3.A1
LEFT JOIN T4 ON T3.C1 = T4.C1
GROUP BY T1.A2, T1.A3
COUNT(DISTINCT T1.A1):T1 為主表,LEFT JOIN 不影響主表行數,等價於 SELECT COUNT(DISTINCT A1) FROM T1 GROUP BY A2, A3。COUNT(DISTINCT T2.B1):T2 為從表,COUNT(DISTINCT) 自動忽略 NULL,等價於 SELECT COUNT(DISTINCT B1) FROM T1 INNER JOIN T2 ON T1.A1=T2.A1 GROUP BY T1.A2, T1.A3。SELECT t1.cnt1 + t2.cnt2 + t3.cnt3
FROM (SELECT COUNT(DISTINCT A1) AS cnt1 FROM T1 GROUP BY A2, A3) t1
JOIN (SELECT COUNT(DISTINCT B1) AS cnt2 FROM T1 INNER JOIN T2 ON T1.A1=T2.A1 GROUP BY T1.A2, T1.A3) t2
JOIN (SELECT COUNT(DISTINCT C1) AS cnt3 FROM T1 INNER JOIN T3 ON T1.A1=T3.A1 INNER JOIN T4 ON T3.C1=T4.C1 GROUP BY T1.A2, T1.A3) t3;
優勢:避免大表 JOIN 後再聚合,改為小結果集 JOIN,記憶體與 CPU 開銷大幅下降。
對於查詢:
SELECT * FROM t1 LEFT JOIN t2 ON t1.c1 = t2.d1 WHERE condition;
若 WHERE 條件中至少存在一個滿足「非空條件」的條件 K,且 K 引用 t2 的任意欄位,則該 LEFT JOIN 可等價轉換為 INNER JOIN。
條件類型 不為 t2.column IS NULL。以下皆為非空條件:
t2.d1 = 5t2.d1 > 10t2.d1 IN (1,2,3)t2.d1 = t1.c2t2.d1 IS NOT NULLt2.d1 LIKE 'abc%'t2.d1 BETWEEN 1 AND 100SELECT * FROM t1
LEFT JOIN t2 ON t1.c1 = t2.d1
LEFT JOIN t3 ON t2.d2 = t3.e1
WHERE t3.e1 = 1; -- t3.e1=1 為非空條件,引用 t3 欄位
-- 步驟 1:t3 層消除
=> SELECT * FROM t1 LEFT JOIN t2 ON t1.c1=t2.d1 INNER JOIN t3 ON t2.d2=t3.e1 WHERE t3.e1=1
-- 步驟 2:t2 層消除 (內層已成 INNER JOIN,t2.d2=t3.e1 為等值條件,滿足非空條件)
=> SELECT * FROM t1 INNER JOIN t2 ON t1.c1=t2.d1 INNER JOIN t3 ON t2.d2=t3.e1 WHERE t3.e1=1
-- 等價於標準 INNER JOIN 鏈
=> SELECT * FROM t1, t2, t3 WHERE t1.c1=t2.d1 AND t2.d2=t3.e1 AND t3.e1=1
原文第 5 節為 Sybase SQL Server 索引策略,核心觀點與 MySQL 通用:
DBCC SHOWCONTIG 檢查碎片、UPDATE STATISTICS 刷新統計、重建索引消除分裂。IS NULL → 改 INNER JOIN。INNER JOIN。COUNT(DISTINCT) + LEFT JOIN → 拆解為獨立聚合再合併。type=ref/eq_ref、rows 合理、Extra 無 Using temporary/filesort。