被驱动表join字段必须有单独单列索引,且类型与字符集须完全一致;驱动表应为小结果集并带高选择性where;left join过滤条件须放on中,否则索引失效。

被驱动表的ON字段索引必须单独存在,不能靠复合索引前缀
MySQL在JOIN时对被驱动表(即非驱动表、右表)的ON字段查索引,只认「最左匹配」。比如ON a.user_id = b.user_id,而b表只有INDEX(created_at, user_id),这个索引**不会被用于JOIN**——因为user_id不是最左列。
实操建议:
- 为每个被驱动表的JOIN字段单独建单列索引,例如
ALTER TABLE orders ADD INDEX idx_user_id (user_id) - 若该字段同时参与WHERE或ORDER BY,再补联合索引,如
INDEX(user_id, status, created_at),但别指望它替代单列索引做JOIN - 用
EXPLAIN确认key列是否命中你刚建的索引;如果仍是NULL,说明索引没被选中,大概率是顺序或类型不匹配
JOIN字段类型与字符集必须完全一致,否则索引直接失效
常见错误是users.id为BIGINT,而orders.user_id为INT;或两边都是VARCHAR但字符集一个是utf8mb4、另一个是latin1。MySQL会隐式转换,但**索引无法用于等值匹配**,type直接降为ALL。
实操建议:
- 用
SHOW CREATE TABLE比对两张表对应字段的DATA_TYPE、COLLATION和CHARACTER_SET - 统一改用
utf8mb4_unicode_ci,数值类型保持位宽一致(如都用BIGINT UNSIGNED) - 避免在ON里写
CAST(b.user_id AS SIGNED)这类转换,它会让索引失效
小结果集必须当驱动表,且WHERE条件要“贴着驱动表”写
MySQL 5.7+虽有JOIN重排能力,但前提是所有表都有可用索引且统计信息准确。一旦某张表缺失关键索引,优化器就放弃重排,老老实实按SQL字面顺序执行——这时如果大表在左,中间结果集爆炸,QPS断崖下跌。
实操建议:
- 把带高选择性WHERE的表放
FROM最左侧,例如SELECT ... FROM orders o WHERE o.status = 'paid' AND o.created_at > '2026-01-01' JOIN users u ON o.user_id = u.id - 用
EXPLAIN FORMAT=TREE看真实执行顺序,别信书写顺序 - 如果明确知道
users过滤后只有100行、orders有百万行,就强制STRAIGHT_JOIN:把users放前面,orders放后面
LEFT JOIN右表字段进WHERE,等于白建索引
写成LEFT JOIN orders o ON u.id = o.user_id WHERE o.status = 'shipped',语义上已退化为INNER JOIN,但优化器仍按外连接逻辑执行:先全量扫users,再为每行去orders找匹配,最后才过滤status。即使orders.status有索引,也救不了。
实操建议:
- 业务真需要左表全量?那就把过滤条件挪到
ON里:LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'shipped' - 不需要保留NULL行?直接改
INNER JOIN,让优化器能跳过无匹配的左表记录 - 检查
EXPLAIN的rows:如果左表rows远大于实际用户数,大概率是这个坑











