加索引是优化nested loop join最有效手段,比调join_buffer_size快一个数量级;确认走inlj需explain中type为ref/eq_ref/const、key非null、extra不含using join buffer;索引必须建在被驱动表关联字段上,且需满足最左前缀、无隐式转换等条件。
加索引是优化 nested loop join 最有效、最直接的手段,比调 join_buffer_size 快一个数量级,且几乎无副作用。
怎么确认当前走的是 Index Nested-Loop Join(INLJ)
执行 EXPLAIN 后重点看三列:
-
type是ref、eq_ref或const—— 说明驱动表用了索引定位 -
key列显示具体使用的索引名(非NULL) -
Extra不含Using join buffer (Block Nested Loop)—— 排除 BNL 场景
只要满足前两项,基本可断定 MySQL 正在用 INLJ:驱动表按索引逐行取值,被驱动表用索引快速匹配,避免全表扫描。
给谁加索引?必须加在被驱动表的关联字段上
很多人误以为“JOIN 字段有索引就行”,其实关键在**被驱动表**(即内层循环那张表)的 ON 条件字段。例如:
SELECT * FROM orders o JOIN users u ON o.user_id = u.id;
若优化器选 orders 为驱动表(常见),则 users.id 必须有索引;若 users 是驱动表,则 orders.user_id 才需要索引。
判断驱动表的方法:
-
LEFT JOIN:左表固定为驱动表 -
INNER JOIN:看EXPLAIN中table列顺序,排在前面的通常是驱动表(但以rows过滤后实际行数最少者为准) - 用
EXPLAIN FORMAT=JSON查"leading_table"字段更可靠
索引怎么建才真正生效
不是随便建个单列索引就完事。需满足:
- 被驱动表关联字段单独建索引(如
user_id),或作为联合索引的**最左前缀** - 避免在 ON 字段上做函数/类型转换:
ON CAST(o.user_id AS CHAR) = u.id会让索引失效 - 注意 NULL 值:若字段允许 NULL 且索引未显式覆盖(如没加
NOT NULL约束),某些 MySQL 版本可能跳过该索引 - 联合索引中,等值条件字段放前,范围条件放后 —— 比如
ON u.status = 'active' AND u.created_at > '2024-01-01',索引应为(status, created_at)
为什么有时候加了索引还是慢
常见陷阱:
- 驱动表本身太大(比如 WHERE 后仍返回几十万行),即使被驱动表走索引,总匹配次数仍爆炸 —— 此时要先缩小驱动表结果集(加 WHERE、改写逻辑、物化中间结果)
- 被驱动表索引存在,但
EXPLAIN显示key为空:可能是统计信息过期,执行ANALYZE TABLE 表名更新 - 字符集/排序规则不一致导致隐式转换,例如
utf8mb4字段 JOINutf8字段,索引无法使用 - 连接字段类型不一致(如
VARCHARvsINT),MySQL 自动转换后索引失效
真正卡住 Nested Loop 性能的,从来不是 buffer 大小,而是“驱动表扫多少行”和“被驱动表能不能用索引跳着找”。这两点没理清,调任何参数都是隔靴搔痒。











