为什么 JOIN
查询变慢?先看执行计划
MySQL 不是“写了
JOIN就自动走索引”,它会根据统计信息选择驱动表和连接方式。最直接的判断方法是加
EXPLAIN看
type、
key、
rows和
Extra字段:
-
type是
ALL或
index?说明没走有效索引,大概率全表扫描
-
key为空?关联字段缺失索引
-
rows数值远大于实际匹配行数?统计信息过期或索引选择错误
-
Extra出现
Using join buffer (Block Nested Loop)?内存不足导致回表或缓存降级
关联字段必须加索引,且注意顺序和类型一致
这是最容易被忽略也最立竿见影的优化点。多表
JOIN中,**被驱动表(非左表)的关联列必须有索引**,且需满足:
- 索引列顺序要匹配
ON条件中的顺序,例如
ON a.id = b.a_id,则
b.a_id单独建索引即可;若写成
ON a.code = b.a_code AND a.status = b.a_status,则推荐联合索引
(a_code, a_status)
- 字段类型必须严格一致:比如
INT对
BIGINT、
VARCHAR(50)对
VARCHAR(100),即使值能隐式转换,也可能拒绝使用索引
- 字符集和排序规则(
COLLATION)也要一致,否则
JOIN时无法用索引做等值匹配
- 避免在关联字段上用函数或表达式,例如
ON DATE(a.create_time) = DATE(b.date)会让索引失效
控制驱动表顺序,小结果集优先做驱动表
MySQL 默认按 FROM 后顺序选驱动表(尤其在
STRAIGHT_JOIN未显式指定时),但优化器有时会选错。可手动干预:
- 用
STRAIGHT_JOIN强制左表为驱动表,适用于你明确知道哪张表过滤后数据量更少
- 把带高选择性
WHERE条件的表放在
FROM最左侧,例如
SELECT ... FROM orders o JOIN users u ON o.uid = u.id WHERE o.status = 'paid' AND o.created_at > '2024-01-01',如果
orders加了
(status, created_at)复合索引,它很可能比
users先返回更少行,就该当驱动表
- 避免在驱动表上用
SELECT *,只查真正需要的字段,减少临时表和网络传输开销
- 若中间表(如关联三张表时的第二张)结果集过大,考虑拆成两个两表
JOIN,用应用层或临时表中转
慎用 LEFT JOIN
,避免 NULL 行拖慢性能
LEFT JOIN的语义是保留左表所有行,右表无匹配则补
NULL。这看似安全,但容易埋坑:
- 如果后续
WHERE条件里写了右表字段(如
WHERE b.status = 'active'),实际上把
LEFT JOIN退化成了
INNER JOIN,但优化器可能仍按外连接逻辑执行,导致多余
NULL行参与计算
- 更隐蔽的问题是:右表没有索引 + 左表大,会导致对左表每行都去扫一遍右表(
Block Nested Loop),性能断崖式下跌
- 替代思路:先用子查询或 CTE 把右表符合条件的数据预聚合好(如
SELECT id, status FROM b WHERE status = 'active'),再与左表
JOIN,往往比原
LEFT JOIN ... WHERE快得多
真正影响
JOIN性能的,往往不是语法本身,而是索引是否覆盖关联路径、驱动表是否被误判、以及 NULL 行是否在无意中放大了计算量。这些地方一动,QPS 可能翻倍,也可能雪崩——得一行行
EXPLAIN看着调。
