場景:三層分級 category → subcategory → item,需查詢某 category 下 item 數 > 2000 的所有 subcategory 完整信息。
SELECT c.id
FROM subcategory AS c
INNER JOIN item AS i ON i.subcategory = c.id
WHERE c.category = 14
GROUP BY c.id
HAVING COUNT(*) > 2000;
SELECT * FROM subcategory
WHERE id IN (
SELECT c.id
FROM subcategory AS c
INNER JOIN item AS i ON i.subcategory = c.id
WHERE c.category = 14
GROUP BY c.id
HAVING COUNT(*) > 2000
);
id=1, select_type=PRIMARY, table=subcategory, type=ALL, rows=300783, Extra=Using where
id=2, select_type=DEPENDENT SUBQUERY, table=c, type=ref, rows=100, Extra=Using where; Using index; Using temporary; Using filesort
id=2, select_type=DEPENDENT SUBQUERY, table=i, type=ref, rows=28, Extra=Using index
災難核心:MySQL 將內層轉為 DEPENDENT SUBQUERY,外層掃描 30 萬行,每行執行一次內層查詢 → 嵌套循環 30 萬次。
SELECT * FROM subcategory AS s
WHERE (
s.id,
(
SELECT c.id
FROM subcategory AS c
JOIN item AS i ON i.subcategory = c.id
WHERE c.category = 14
GROUP BY c.id
HAVING COUNT(0) > 2000
AND (s.id) = (c.id)
)
);
SELECT * FROM subcategory
WHERE id IN (
SELECT id FROM (
SELECT c.id
FROM subcategory AS c
INNER JOIN item AS i ON i.subcategory = c.id
WHERE c.category = 14
GROUP BY c.id
HAVING COUNT(*) > 2000
) AS x
);
關鍵:最內層包裹 SELECT id FROM ( ... ) AS x,優化器識別為獨立衍生表,先物化到臨時表(僅 10 行),外層再關聯,秒級完成。
SELECT s.*
FROM subcategory AS s
INNER JOIN (
SELECT c.id
FROM subcategory AS c
INNER JOIN item AS i ON i.subcategory = c.id
WHERE c.category = 14
GROUP BY c.id
HAVING COUNT(*) > 2000
) AS t ON s.id = t.id;
優勢:語義清晰、穩定走 Join 邏輯、不依賴優化器版本行為、易維護。
EXPLAIN 看 select_type 是否出現 DEPENDENT SUBQUERY。WHERE ... IN (SELECT ...)、WHERE ... NOT IN (SELECT ...)。derived_merge=on),若需強制物化可用 /*+ MAX_EXECUTION_TIME(1) */ 或關閉 optimizer_switch='derived_merge=off'。UPDATE t SET ... WHERE id IN (SELECT id FROM t ...) 報錯,需用多表 Join 或衍生表繞過。MySQL 歷史版本對獨立子查詢優化不足是已知限制。開發者應養成「優先 Join、謹慎 IN/EXISTS、必跑 EXPLAIN」習慣,將性能風險消滅在代碼評審階段。