JOIN字段没索引,查询直接变慢十倍
MySQL执行
JOIN时,如果关联字段(比如
ON t1.id = t2.t1_id中的
t1.id或
t2.t1_id)没有索引,优化器大概率会走全表扫描。尤其当被驱动表(通常是
JOIN右边的表)数据量稍大,性能断崖式下跌。不是“可能慢”,是几乎必然慢。
实操建议:
对ON子句中出现的每个字段,单独检查是否已有索引:
SHOW INDEX FROM table_name;若字段常用于
JOIN且区分度高(如外键),优先建
B-tree单列索引;不要依赖联合索引的“前缀匹配”来支撑
JOIN,除非该字段恰好是联合索引最左列 注意:主键自动有索引,但外键列不会自动创建索引——哪怕你加了
FOREIGN KEY约束,也必须显式建索引
驱动表选错导致Extra: Using join buffer
MySQL默认用
BNL(Block Nested-Loop)算法处理
JOIN,当驱动表小、被驱动表大且后者无可用索引时,就会启用
join_buffer。这时
EXPLAIN里会出现
Extra: Using join buffer,意味着大量数据被载入内存做嵌套循环,I/O和CPU压力陡增。
关键判断点:
看EXPLAIN输出的
table列顺序:排在前面的是驱动表,后面的是被驱动表 确保驱动表是结果集最小的那个(不一定是物理行数最少,而是经过
WHERE过滤后实际参与
JOIN的行数最少) 用
STRAIGHT_JOIN强制指定驱动表顺序(仅当确认优化器选错时):
SELECT STRAIGHT_JOIN ... FROM small_table t1 JOIN large_table t2 ON t1.id = t2.t1_id;
join_buffer_size调大能缓解但治标不治本;真正要解决的是让被驱动表能走索引查找(即上一条说的字段加索引)
复合条件JOIN时,联合索引怎么设计
当
JOIN同时带
WHERE过滤(如
ON t1.id = t2.t1_id WHERE t2.status = 'active'),只给
t2.t1_id建单列索引往往不够。优化器可能放弃使用该索引,转而全表扫描再过滤。
此时应建覆盖
JOIN+
WHERE条件的联合索引: 把
JOIN字段放最左(因
ON匹配需最左前缀) 紧接
WHERE中的等值条件字段(如
status) 最后可加
SELECT中需要的其他字段,实现“索引覆盖”,避免回表 示例:对
t2建索引
(t1_id, status, name),适用于
JOIN ... ON t1.id = t2.t1_id WHERE t2.status = 'active'注意:
ORDER BY或
GROUP BY字段若也出现在查询中,可考虑加入联合索引末尾,但需权衡写入开销
EXPLAIN中type=ALL或type=index是危险信号
EXPLAIN输出里的
type字段直接反映访问类型。
ALL代表全表扫描,
index代表全索引扫描(仍需遍历整个索引树),这两种都说明没命中有效索引用于定位
JOIN行。
排查步骤:
先确认key列是否为
NULL——是则肯定没走索引 若
key非空但
type仍是
ALL或
index,常见原因是:字段类型不一致(如
INTvs
VARCHAR)、隐式类型转换、函数包裹字段(如
ON t1.id = CAST(t2.t1_id AS SIGNED)) 检查字符集和排序规则是否完全一致:
SHOW CREATE TABLE对比两边字段的
COLLATE避免在
ON字段上使用函数、表达式或
LIKE '%xxx'这类无法利用索引的操作 联合索引的设计粒度、驱动表的实际大小估算、以及隐式类型转换这类细节,比“加个索引”本身更影响效果。很多慢查询修复不了,卡就卡在这里。
