LEFT JOIN索引必须建在右表ON字段上,因左表为驱动表、右表为被驱动表,每次左表一行均需右表索引快速匹配;复合条件须按ON顺序建联合索引,右表WHERE条件应移入ON子句避免退化为INNER JOIN。

LEFT JOIN 的索引必须建在右表(被驱动表)的 ON 字段上
LEFT JOIN 默认以左表为驱动表,右表是被驱动方——这意味着每次左表输出一行,MySQL 就要到右表里按 ON 条件查一次。如果右表没索引,就会全表扫描,10 万行驱动 × 100 万行右表 = 100 亿次行扫描。
常见错误是给左表加索引,比如 ALTER TABLE classes ADD INDEX idx_card(card),但 classes 是驱动表,有没有索引不影响它被全读;真正要加速的是右表 book 的 card 字段:
ALTER TABLE book ADD INDEX idx_card(card);
- 复合
ON条件(如ON t1.a = t2.x AND t1.b = t2.y)时,右表必须建联合索引INDEX(x, y),顺序必须和ON中一致 -
INDEX(a, b, c)无法加速ON t2.b = ?,因为b不是最左前缀 - 右表的
WHERE条件字段(如s.gender = 'F')如果写在ON子句里,也应包含进联合索引,比如INDEX(class_id, gender)
LEFT JOIN 右表的 WHERE 条件不能写在 WHERE 子句里
把右表过滤条件放在 WHERE 会导致 LEFT JOIN 退化成 INNER JOIN。例如:
SELECT c.name, COUNT(s.id) FROM classes c LEFT JOIN students s ON c.id = s.class_id WHERE s.gender = 'F' GROUP BY c.name;
这条语句实际只返回有女学生的班级,其他班级被 WHERE 过滤掉了——因为 s.gender 是 NULL 时无法满足 = 'F'。
正确做法是把右表条件移到 ON:
SELECT c.name, COUNT(s.id) FROM classes c LEFT JOIN students s ON c.id = s.class_id AND s.gender = 'F' GROUP BY c.name;
- 这样能保留所有班级,没女学生的班级计数为 0
- 同时这个
AND s.gender = 'F'让优化器更可能用上INDEX(class_id, gender) - 如果仍用单列索引
INDEX(class_id),gender过滤只能在内存中做,效率低
驱动表太大时,别硬扛,换思路
LEFT JOIN 左表是 50 万行,右表是 1 万行,但业务又要求左表必须是主表——这时候强行优化索引效果有限,得换执行逻辑:
- 先用
WHERE提前缩小左表结果集,比如加WHERE c.status = 'active',让驱动行数从 50 万降到 5 千 - 考虑用
EXISTS替代仅判断存在的 LEFT JOIN,例如查“没有订单的客户”:SELECT * FROM customer c WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id) - 如果右表只需取一条最新记录(如最新订单时间),用相关子查询比 JOIN +
GROUP BY更稳:(SELECT created_at FROM orders o WHERE o.customer_id = c.id ORDER BY created_at DESC LIMIT 1)
STRAIGHT_JOIN 和 EXPLAIN 是你唯一能信的依据
别猜优化器怎么选驱动表,用 EXPLAIN 看 rows 列——它反映的是 WHERE 过滤后每张表预计扫描的行数,不是总行数。如果发现右表 rows 很大,说明它被当成了驱动表(比如用了 RIGHT JOIN 或嵌套子查询),或者索引根本没生效。
必要时用 STRAIGHT_JOIN 强制顺序,但只在确认估算准确时用:
SELECT STRAIGHT_JOIN c.name, s.name FROM classes c LEFT JOIN students s ON c.id = s.class_id;
-
STRAIGHT_JOIN会忽略优化器成本估算,强制按书写顺序执行 - 如果左表实际过滤后只剩 100 行,而优化器误判为 10 万行并选了右表当驱动,这时加
STRAIGHT_JOIN反而更快 - 但一旦估算错(比如统计信息过期),
STRAIGHT_JOIN会让性能雪崩,务必配合ANALYZE TABLE更新统计
最常被忽略的一点:字段类型不一致会直接让索引失效。比如 class_id 是 INT,s.class_id 是 BIGINT,或一边是 VARCHAR 一边是 CHAR,MySQL 会隐式转换,导致右表索引完全不用。检查 SHOW CREATE TABLE,确保两边类型严格相同。


















