同一個業務需求,寫法不同,效能相差 3000 倍。本文透過「每類別最小值」查詢案例,揭示相關子查詢與分組聚合的本質差異。
表 my_tbl(a, b, c),查詢每個 a 值下 b 為最小值的完整記錄。
樣本數據 80 萬行,期望結果:
a b c 1 3 'cd' 2 3 'nhd' 3 7 'vsd'
SELECT A.* FROM my_tbl AS A WHERE A.b = (SELECT MIN(b) FROM my_tbl AS B WHERE B.a = A.a);
執行 1 小時未出結果。 原因:外層 80 萬行,每行觸發一次子查詢,內層需掃描同組數據,複雜度 O(N²),且無法利用索引下推。
SELECT a, b, c, COUNT(a)
FROM (
SELECT a, b, c FROM my_tbl GROUP BY a, b
) AS A
GROUP BY a;
邏輯:先按 (a, b) 分組 → 每組取第一行(因按 b 升序,即最小 b)→ 外層按 a 再分組取首行。
優化器可使用 INDEX (a, b) 完成 Loose Index Scan,僅掃描各組首行。
| 維度 | 相關子查詢 | 分組聚合 |
|---|---|---|
| 執行模式 | 逐行嵌套循環 | 單次掃描 + 流式聚合 |
| 索引利用 | 難以利用複合索引 | 可觸發 Loose Index Scan |
| 複雜度 | O(N × GroupSize) | O(N) 或 O(GroupCount) |
| 80萬行耗時 | > 3600 秒 | 1.1 秒 |
ROW_NUMBER() OVER (PARTITION BY ...)(MySQL 8.0+)GROUP BY 技巧或用戶變量模擬(a, b) 支援分組掃描更多 MySQL 進階技巧請參考專題:《MySQL 事務操作技巧匯總》、《MySQL 存儲過程技巧大全》、《MySQL 數據庫鎖相關技巧匯總》及《MySQL 常用函數大匯總》。