JOIN 是多表查詢核心操作。本文系統梳理 INNER/LEFT/RIGHT/CROSS/FULL JOIN 語法與集合關係,深度講解 ON/WHERE 執行順序差異、子查詢改 JOIN、冗餘索引等十大性能優化法則,並給出 MySQL 不支援 FULL JOIN 的變通方案。
| 類型 | 關鍵字 | 結果集特徵 | 集合關係 |
|---|---|---|---|
| 內連接 | INNER JOIN / JOIN | 雙方均匹配的行 | A ∩ B |
| 左連接 | LEFT [OUTER] JOIN | 左表全量 + 右表匹配(無則 NULL) | A ∪ (A∩B) |
| 右連接 | RIGHT [OUTER] JOIN | 右表全量 + 左表匹配(無則 NULL) | B ∪ (A∩B) |
| 交叉連接 | CROSS JOIN | 笛卡爾積(全排列) | A × B |
| 全連接 | FULL [OUTER] JOIN | 雙方全量,無匹配填 NULL | A ∪ B |
MySQL 注意:CROSS JOIN` = `INNER JOIN` = `JOIN`(無 ON 時為笛卡爾積),不支援 FULL JOIN,需用 LEFT JOIN UNION RIGHT JOIN 模擬。
-- 表 A id | name 1 | Pirate 2 | Monkey 3 | Ninja 4 | Spaghetti -- 表 B id | name 1 | Rutabaga 2 | Pirate 3 | Darth Vader 4 | Ninja
SELECT * FROM A INNER JOIN B ON A.name = B.name; -- 結果:Pirate/Pirate, Ninja/Ninja
SELECT * FROM A LEFT JOIN B ON A.name = B.name; -- A 全量,Monkey/Spaghetti 右側 NULL
SELECT * FROM A RIGHT JOIN B ON A.name = B.name; -- B 全量,Rutabaga/Darth Vader 左側 NULL
SELECT * FROM A CROSS JOIN B; -- 16 行笛卡爾積
SELECT * FROM A LEFT JOIN B ON A.name = B.name UNION SELECT * FROM A RIGHT JOIN B ON A.name = B.name;
SELECT * FROM a INNER JOIN b ON a.id=b.id 與 SELECT * FROM a,b WHERE a.id=b.id 執行計劃一致,但顯式語法可讀性強、易維護、避免笛卡爾積風險,強制規範使用顯式。
原理:ON 在匹配階段過濾,WHERE 在匹配後過濾。外連接中,ON 決定保留哪些外表行,WHERE 可能將外連接隱式轉為內連接。
-- 差:WHERE 過濾 SELECT * FROM A LEFT JOIN B ON A.name=B.name LEFT JOIN C ON C.name=B.name WHERE C.status>1 AND D.status=1; -- 優:ON 下推 SELECT * FROM A INNER JOIN B ON B.name=A.name LEFT JOIN C ON C.name=B.name AND C.status>1 LEFT JOIN D ON D.id=C.id AND D.status=1;
-- ON 過濾:保留左表所有行,右表不匹配置 NULL SELECT * FROM product LEFT JOIN details ON product.id=details.id AND details.id=2; -- WHERE 過濾:先連接再過濾,相當於內連接 SELECT * FROM product LEFT JOIN details ON product.id=details.id WHERE details.id=2;
-- 子查詢 INSERT INTO t1(a) SELECT b FROM t2 WHERE NOT EXISTS (SELECT 1 FROM t1 WHERE t1.id=t2.r_id); -- JOIN 改寫 INSERT INTO t1(a) SELECT b FROM t2 LEFT JOIN (SELECT DISTINCT id FROM t1) t1 ON t1.id=t2.r_id WHERE t1.id IS NULL;
STRAIGHT_JOIN 強制順序。-- 悲劇:索引失效
WHERE account_id + 1 = 5;
WHERE TO_DAYS(NOW()) - TO_DAYS(create_time) <= 10;
-- 正確:隔離列
WHERE account_id = 4;
WHERE create_time >= DATE_SUB('2023-01-01', INTERVAL 10 DAY);
-- SQL: WHERE payee=72478814 AND amount>1.00 -- 正確索引:(payee, amount) -- 等值列前,範圍列後 -- 錯誤索引:(amount, payee) -- 範圍列導致後續列不可用
SELECT a,b FROM t WHERE c=1 → 索引 (c,a,b),Extra 出現 Using index,避免回表隨機 I/O。
ANALYZE TABLE 更新統計信息(InnoDB 採樣、非阻塞)。OPTIMIZE TABLE 整理碎片(鎖表,大表用 pt-online-schema-change)。LEFT JOIN` 後 `WHERE right.col = val` 將外連接轉為內連接。ON` 條件缺少驅動列索引,導致被驅動表全掃描。