17370845950

mysql中连接查询中的索引优化技巧
JOIN字段必须加索引,否则被驱动表将全表扫描;索引需建在ON子句列上,注意类型一致、最左前缀及避免函数操作。

JOIN 字段必须加索引,否则直接变全表扫描

MySQL 在执行 INNER JOINLEFT JOIN 等连接操作时,如果 ON 条件中的字段没有索引,优化器大概率放弃使用索引,对被驱动表做全表扫描。哪怕主表只返回 1 行,被驱动表仍可能扫几万行。

常见错误现象:EXPLAIN 结果中 typeALLindexrows 值远超预期,Extra 出现 Using join buffer (Block Nested Loop)

  • 索引必须建在 ON 子句里实际参与比较的列上,不是 SELECT
  • 复合索引要注意最左前缀:若写 ON t1.a = t2.a AND t1.b = t2.b,则 t2(a,b) 有效,t2(b,a) 通常无效
  • 关联字段类型要严格一致:比如 INTBIGINT 隐式转换会导致索引失效;VARCHAR(50)VARCHAR(100) 一般不影响,但字符集或排序规则不同(如 utf8mb4_0900_as_cs vs utf8mb4_general_ci)会拒绝走索引

驱动表选择影响索引是否生效

MySQL 的嵌套循环连接(Nested-Loop Join)中,先查的表叫驱动表,后查的叫被驱动表。优化器通常选小结果集作驱动表,但有时判断失误,导致本该走索引的被驱动表被迫全表扫描。

可强制指定驱动顺序:用 STRAIGHT_JOIN(仅限 INNER JOIN),把预估更小的表放在 FROM 后,大表放 JOIN 后,并确保大表的 ON 字段有索引。

SELECT STRAIGHT_JOIN a.id, b.name 
FROM small_table a 
JOIN big_table b ON a.ref_id = b.id;
  • 检查 EXPLAINtable 列顺序,确认哪张是驱动表
  • rows 值大的那张表,务必确保其 ON 字段有索引
  • 避免在 ON 条件中对字段做函数操作,例如 ON YEAR(t1.create_time) = t2.year 会让 t1.create_time 索引失效

覆盖索引 + JOIN 可避免回表,但需注意字段顺序

当被驱动表的查询字段全部包含在某个索引中(即覆盖索引),MySQL 就不用回主键索引捞数据,能显著减少 I/O。但这要求索引把 ON 字段放在前面,查询字段放后面。

例如:需要 SELECT b.name, b.status FROM a JOIN b ON a.bid = b.id,则推荐建索引 b(id, name, status),而不是 b(name, status, id) —— 后者无法用于 ON 匹配。

  • 复合索引中,等值条件字段(=)放最左,范围条件(>, BETWEEN)放中间,查询字段放最后
  • 如果 SELECT 中用了 ORDER BY b.name 且想避免 filesort,索引需包含 name 并满足排序需求(如 b(id, name) 可支撑 ORDER BY name,但前提是 id 是等值过滤)
  • WHERE 条件里的单表过滤字段也应纳入索引,例如 WHERE a.type = 1 AND b.status = 'active',则 b(id, status, name) 更优

多表 JOIN 时,中间表索引容易被忽略

三张及以上表连接时,MySQL 会按一定顺序执行两两 JOIN。第二步 JOIN 的“被驱动表”可能是第一步的结果集(临时表),但更多时候仍是原始物理表。很多人只给首尾两张表建索引,漏掉中间表的 ON 字段索引。

典型场景:orders JOIN order_items ON orders.id = order_items.order_id JOIN products ON order_items.product_id = products.id。这里 order_items 是中间表,它既被 orders 驱动(需 order_id 索引),又被 products 驱动(需 product_id 索引)——两个字段都得单独或联合建索引。

  • EXPLAIN FORMAT=TREE(MySQL 8.0+)看真实执行计划,确认每一步的驱动/被驱动关系
  • 中间表上建议建联合索引,如 order_items(order_id, product_id),兼顾两个方向的 JOIN
  • 避免 SELECT *,尤其在多表 JOIN 中,它会放大回表和临时表开销,让索引收益打折
索引不是建了就万事大吉,关键得出现在执行计划里真正被用上的位置——而那个位置,往往藏在驱动顺序、字段类型、索引结构的细节里。