MySQL 5.7 仍不支援 Hash Join,唯一的連接算法是 Nested-Loop Join(NLJ)及其變體。雖然 Oracle 12c 早已實現 Hash Join、Sort-Merge Join,但理解 NLJ 的演進邏輯,對於寫出高效 JOIN SQL、調優執行計劃至關重要。
for each row in t1 matching range {
for each row in t2 matching ref_key {
for each row in t3 { -- 全表掃描
if join_condition send to client
}
}
}
對應 EXPLAIN type:range → ref → ALL。
驅動表(最外層)通常是結果集最小的表,MySQL 優化器會自動選擇。若驅動表為大表(type=ALL),內層表每行都要掃描一次外層結果,複雜度 O(M×N),資料量大時極其緩慢。
MySQL 為減少內表掃描次數,引入 join_buffer:
for each row in t1 matching range {
for each row in t2 matching ref_key {
store used columns from t1,t2 in join_buffer
if buffer_full {
for each row in t3 {
for each t1,t2 combo in join_buffer {
if join_condition send to client
}
}
empty buffer
}
}
}
-- 處理剩餘 buffer
if buffer_not_empty {
for each row in t3 {
for each t1,t2 combo in join_buffer { ... }
}
}
核心邏輯:將外層循環的所需欄位批次緩存至 join_buffer,內層表每行與 buffer 中所有組合比對,將內表掃描次數從「外層行數」降為 ceil(外層資料量 / join_buffer_size) 次。
掃描次數公式:
(S × C) / join_buffer_size + 1
join_buffer_size ≥ 所有外層組合大小時,內表僅掃描一次,逼近 Hash Join 效果當 JOIN 條件欄位有索引,MySQL 傾向使用 Index NLJ(對應 EXPLAIN ref/eq_ref),直接透過索引查找匹配行,避免全表掃描。此時 join_buffer 不生效。
若 JOIN 欄位無索引,Simple NLJ 會導致內表重複全掃描,資料量稍大查詢可能跑「幾年」出不來結果。MySQL 會強制使用 Block NLJ(有 join_buffer 時)或提示建立索引。結論:大表 JOIN 必須有索引支援,否則依賴 BNL 也是杯水車薪。
STRAIGHT_JOIN 強制指定小結果集為驅動表(左表)| 算法 | 適用場景 | 內表掃描次數 | 關鍵參數 |
|---|---|---|---|
| Simple NLJ | 極小表、有索引 | 外層行數 | 無 |
| Block NLJ | 無索引、中等結果集 | ceil(外層資料量/buffer) | join_buffer_size |
| Index NLJ | JOIN 欄位有索引 | 1(索引查找) | 索引設計 |
理解 NLJ 演進,本質是理解「如何減少內表存取」。下一代 MySQL 已引入 Hash Join,但 NLJ 仍是基石,索引設計與驅動表選擇永遠是第一生產力。