判断join是否走索引应先执行explain,重点看type和key字段:type为eq_ref、ref、range或const且key非null即走索引;type=all/index或key=null表示未走索引,需检查类型一致、无函数/隐式转换、符合最左前缀等条件。

看EXPLAIN的type和key字段最直接
执行 EXPLAIN 是判断 JOIN 是否走索引的第一步,别跳过。重点盯两个字段:type 和 key。
type 值为 eq_ref、ref、range 或 const 表示走了索引;若出现 ALL 或 index,说明对应表被全表扫描了。
key 字段显示实际使用的索引名,值为 NULL 就等于没用上索引——哪怕你建了索引,也可能因类型不匹配、函数包裹或隐式转换而失效。
- 对左表和右表都要分别看
EXPLAIN输出中的对应行(多表 JOIN 时会有多个table行) - 如果某张表的
type=ALL且key=NULL,基本可断定它拖慢了整个 JOIN - 注意
possible_keys不为空但key为NULL:优化器认为走索引反而更慢,常因统计信息不准或索引选择性差
检查JOIN字段是否满足索引使用前提
索引存在 ≠ 索引被用。JOIN 字段必须同时满足几个硬条件,缺一不可:
- 两张表的连接列类型必须严格一致(比如都是
BIGINT,不能一边是VARCHAR(20)一边是INT) - 字符集和
collation要相同,否则可能触发隐式转换,例如utf8mb4_unicode_ci和utf8mb4_bin混用会导致索引失效 - 不能在 ON 条件里对索引列做函数操作,如
ON UPPER(a.code) = UPPER(b.code)或ON DATE(created_at) = '2026-09-17' - 复合索引要遵循最左前缀原则:若索引是
(user_id, status),而 JOIN 条件只用了status,该索引不会被选中
用Handler_read_key验证真实索引调用量
执行计划是预估,Handler_read_key 是运行时真实计数。它代表通过索引查找定位到具体行的次数,比 EXPLAIN 更反映实际负载。
在 JOIN 执行前后分别运行:
SHOW STATUS LIKE 'Handler_read_key';
观察增量是否与预期 JOIN 行数接近。如果增量极小,但 Handler_read_rnd_next(随机读行数)暴涨,说明大量回表或全表扫描正在发生。
- 该指标对单条 SQL 不敏感,适合在压测或慢查询复现后抓取快照对比
- 注意:它统计的是整个会话或全局,高并发下建议用
SELECT CONNECTION_ID()配合会话级状态查看 - 如果
Handler_read_key / (Handler_read_key + Handler_read_rnd_next)低于 85%,说明索引整体利用率偏低,需排查是否多数 JOIN 查询都绕过了索引
大表JOIN校验时容易忽略的隐性陷阱
当表数据量超千万,光看执行计划和字段定义还不够。真正卡住的往往是那些不报错、不告警、但让 JOIN 变成“伪索引”的细节:
- 右表 JOIN 字段允许
NULL,而左表不允许:MySQL 可能放弃使用该索引,尤其在LEFT JOIN中WHERE b.id IS NULL场景下 - 统计信息过期:
ANALYZE TABLE没跑过,优化器基于错误基数选择嵌套循环而非哈希连接,也表现为type=ALL - 字符型主键带前导空格或不可见字符:肉眼看不出差异,但
LENGTH(TRIM(id))和HEX(id)一查就露馅 - 分区表未按 JOIN 字段分区:即使字段有索引,跨分区扫描仍等效于多次小范围全扫
这些点不会在 EXPLAIN 里明说,但会实实在在让 ref 变成 ALL,或者让 key 显示索引名却毫无性能提升。










