在 LEFT JOIN 中,左表不一定是驅動表。MySQL 優化器根據 WHERE 條件、索引可用性、預估掃描行數動態決定。錯誤假設左表為驅動表會導致索引規劃失效、出現 Using temporary; Using filesort。
categories 與文章表 postsCREATE TABLE `categories` (
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(15) NOT NULL,
`created` DATETIME NOT NULL,
KEY `name` (`name`)
) ENGINE=InnoDB;
CREATE TABLE `posts` (
`id` INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
`category_id` INT UNSIGNED NOT NULL,
`title` VARCHAR(100) NOT NULL,
`content` VARCHAR(200) NOT NULL,
`created` DATETIME NOT NULL,
KEY `category_id` (`category_id`),
KEY `created` (`created`),
KEY `category_id_created` (`category_id`, `created`)
) ENGINE=InnoDB;
EXPLAIN SELECT *
FROM posts
LEFT JOIN categories ON posts.category_id = categories.id
WHERE categories.name LIKE 'foobar%'
ORDER BY posts.created DESC;
EXPLAIN 結果:
| table | key | Extra |
|---|---|---|
| categories | name | Using where; Using temporary; Using filesort |
| posts | category_id |
分析: 優化器選 categories 為驅動表(因 WHERE 使用其索引 name),但 ORDER BY 用從表 posts.created,導致需建立臨時表排序,效能極差。
EXPLAIN SELECT *
FROM posts
LEFT JOIN categories ON posts.category_id = categories.id
WHERE categories.id = 123
ORDER BY posts.created DESC;
結果: 無 Using temporary/filesort。因驅動表 categories 僅 1 行(常數表),從表可直接走 category_id_created 索引有序掃描。
EXPLAIN SELECT *
FROM posts
LEFT JOIN categories ON posts.category_id = categories.id
WHERE posts.status = 'published'
ORDER BY posts.created DESC;
此時 posts 成為驅動表,ORDER BY 走聚簇索引或 created 索引,無額外排序開銷。
Using filesort。(WHERE欄位, JOIN欄位, ORDER BY欄位) 覆蓋查詢。LEFT JOIN 效能關鍵在於「驅動表確定」與「索引覆蓋」。寫 SQL 前先 EXPLAIN,觀察 table 順序與 Extra 欄位,依據實際執行計畫調整條件分布與索引,而非憑經驗猜測左表必為驅動表。