LEFT JOIN右表未走索引,主因是连接条件不满足最左前缀匹配或驱动表结果集过大导致优化器弃用NLJ而选BNL;需确保被驱动表连接字段有合适索引且类型、字符集完全一致。

为什么LEFT JOIN的右表没走索引,明明字段上有索引?
MySQL 5.7+ 默认用 BNL(Block Nested-Loop)算法跑 LEFT JOIN,但**被驱动表是否走索引,不取决于“有没有索引”,而取决于连接条件能否触发索引的最左前缀匹配,且驱动表结果集不能太大**。常见错误是只在被驱动表的外键字段建单列索引,却忽略驱动表 WHERE 条件过滤后实际返回的行数——一旦驱动表输出超几千行,优化器大概率放弃索引嵌套循环(NLJ),退化为全表扫描右表。
- 检查执行计划:
EXPLAIN中看type是否为ref或eq_ref,而非ALL或index - 被驱动表的连接字段必须有索引,且该索引最好以连接列为最左字段;如果还有额外查询条件(如
WHERE order_status = 'paid'),建议建联合索引:比如(order_user_id, order_status) - 驱动表加 LIMIT 不影响被驱动表索引选择——优化器基于预估行数决策,不是实际返回数
INNER JOIN 时谁当驱动表?MySQL 怎么选的
优化器按「预估扫描行数更少的表」作为驱动表,但这个预估依赖统计信息准确度。你看到 EXPLAIN 里 rows 值不准,往往是因为 ANALYZE TABLE 没跑过,或者表数据变更剧烈后统计信息过期。
- 用
SHOW INDEX FROM table_name确认关键字段索引是否存在、是否失效(Cardinality为 0 表示可能异常) - 强制指定驱动顺序:用
STRAIGHT_JOIN(仅限INNER JOIN),例如SELECT ... FROM t1 STRAIGHT_JOIN t2 ON t1.id = t2.t1_id,此时t1必为驱动表 - 避免在驱动表连接字段上用函数或表达式,比如
ON DATE(t1.create_time) = t2.date_key会直接让索引失效
外键字段建了索引,但JOIN还是慢,是不是索引没生效?
外键约束本身不自动创建索引,必须手动建。即使建了,也可能因类型不一致导致隐式转换,使索引失效。典型场景是驱动表字段为 INT,被驱动表外键是 BIGINT 或 VARCHAR,MySQL 会把整列转成浮点再比对。
- 确认字段类型完全一致:
DESCRIBE table_name对比t1.user_id和t2.ref_user_id的Type列 - 字符集/排序规则也要一致,比如
utf8mb4_0900_as_cs和utf8mb4_general_ci联合比较时无法走索引 - 用
EXPLAIN FORMAT=JSON查看used_columns和key_parts,确认优化器实际用了哪些索引字段
多层JOIN(A→B→C)怎么安排索引才不爆炸
三层及以上 JOIN 容易触发“中间结果膨胀”,即 A 与 B 关联后生成 10 万行,再和 C 关联就变成千万级扫描。这时光靠单表索引没用,得控制中间集大小,或改写逻辑。
- 优先在每层 JOIN 的被驱动表连接字段建索引,顺序按执行计划中实际驱动顺序来——别按 SQL 书写顺序猜
- 如果 C 表只取少量字段(如
C.status),考虑把 C 表相关条件下推到子查询,例如先SELECT c_id FROM c WHERE c.type = 'active'再 JOIN,减少关联基数 - 注意
JOIN顺序受括号影响:A JOIN (B JOIN C)和(A JOIN B) JOIN C可能产生不同执行计划,必要时用括号显式控制
最麻烦的是跨库或分库场景,外键索引再好也救不了网络往返和分布式 JOIN 的开销——这时候得承认,索引设计只是局部解,架构层面该拆就拆,别硬扛。











