JOIN 字段必须有索引,否则就是全表扫描
MySQL 在执行
INNER JOIN、
LEFT JOIN时,如果连接条件(如
ON t1.user_id = t2.user_id)中任意一侧字段没索引,优化器大概率放弃使用索引,转为嵌套循环 + 全表扫描。尤其当被驱动表(右表)数据量大时,性能断崖式下降。 检查方式:
EXPLAIN SELECT ... JOIN ...中看
type是否为
ALL或
index,
key列是否为
NULL索引要建在
ON子句实际参与比较的列上,不是
WHERE里的列 —— 即使 WHERE 条件更“关键”,没在 ON 里用,对 JOIN 效率无直接帮助 复合索引需注意顺序:若写
ON a.x = b.x AND a.y = b.y,则
b(x,y)索引有效,
b(y,x)通常无效(除非 y 是常量)
驱动表选择影响巨大,小结果集应作驱动表
MySQL 的 JOIN 是单向嵌套循环:先取驱动表(左表)一行,再根据 ON 条件去被驱动表(右表)查匹配行。所以驱动表越小,外层循环次数越少,整体 I/O 和 CPU 开销越低。
优化器一般能自动选,但复杂查询(含子查询、函数、OR 条件)可能误判;可用STRAIGHT_JOIN强制指定驱动表顺序 避免在驱动表上用
SELECT *或大字段(如
TEXT),减少内存拷贝和网络传输开销 如果 LEFT JOIN 的左表加了
WHERE过滤但没走索引,可能导致实际驱动表变大——先确认
WHERE是否命中索引,再谈 JOIN 顺序
避免在 JOIN 条件中使用函数或表达式
一旦在
ON或
USING中对字段做运算,比如
ON DATE(t1.created_at) = DATE(t2.date),该字段就无法使用索引 —— MySQL 无法将函数结果与索引 B+ 树结构对齐。 正确做法是预计算或改写逻辑:例如把日期范围下推到 WHERE,用
t1.created_at >= '2024-01-01' AND t1.created_at 替代 <code>DATE()同理,
ON UPPER(t1.name) = UPPER(t2.name)会失效;若业务允许,统一存小写并建函数索引(MySQL 8.0+ 支持
CREATE INDEX idx_name ON t1 ((LOWER(name)))) 隐式类型转换也危险:
ON t1.id = t2.user_id中若
t1.id是
INT、
t2.user_id是
VARCHAR,MySQL 会把后者全转成数字比对,索引失效
覆盖索引 + 延迟关联可大幅减少回表
当 JOIN 后还要查大量非索引字段(如
SELECT t1.*, t2.content),MySQL 不得不反复回主键索引捞数据,I/O 成倍增加。覆盖索引本身不能跨表,但可通过“延迟关联”模式规避。 典型写法:
SELECT t1.*, t2.content FROM t1 INNER JOIN (SELECT id FROM t2 WHERE status=1) AS t2_ids ON t1.t2_id = t2_ids.id INNER JOIN t2 ON t2.id = t2_ids.id—— 先用最小结果集驱动,再补全字段 确保子查询
(SELECT id FROM t2 WHERE ...)能走索引(哪怕只是
status单列索引),且返回行数可控 如果业务允许,尽量让
SELECT只取必要字段,配合联合索引把常用查询字段都包含进去(如
INDEX idx_cover (a,b,c)支持
SELECT a,b WHERE a=? AND b=?) 真正卡住性能的,往往不是 JOIN 本身,而是连接字段缺失索引、驱动表意外膨胀、或者一个看似无害的函数调用悄悄让整个索引失效。这些点不查
EXPLAIN几乎发现不了。
