ON中多个等值条件必须用顺序一致的复合索引,单列索引无法协同生效;字段顺序需按ON出现顺序及选择性高低排列,且须通过EXPLAIN验证key_len和Extra(如无Using join buffer)确认全字段命中。

ON里多个等值条件,索引必须是复合的,不是单列索引堆砌
多列JOIN(比如 ON a.x = b.x AND a.y = b.y)不会自动用上两个单列索引。MySQL、PostgreSQL 等优化器通常只选其中一个字段走索引,另一个靠过滤——rows 值会暴增,EXPLAIN 里 key_len 只显示单列长度就是铁证。
必须为每张参与 JOIN 的表,分别建顺序一致的复合索引:
CREATE INDEX idx_orders_user_tenant ON orders(user_id, tenant_id);CREATE INDEX idx_users_id_tenant ON users(id, tenant_id);
注意:字段顺序要和 ON 中出现的顺序尽量一致;如果 ON 是 o.tenant_id = u.tenant_id AND o.user_id = u.id,那索引就得是 (tenant_id, user_id),否则可能只用上第一个字段。
复合索引字段顺序不能按“主键习惯”排,得看选择性和查询模式
很多人一上来就写 (user_id, tenant_id),觉得 user_id 是主键相关字段更“重要”,但实际效果可能很差。真正该优先放前面的是:
- 高选择性字段(比如
tenant_id只有几十个值,而user_id是百万级唯一值 → 反而是user_id更适合作首列) - WHERE 或 ON 中更常作为等值条件的字段(例如多数查询都带
WHERE tenant_id = ?,那它就该放最左) - 避免把低区分度字段(如
status、is_deleted)放在最左,否则索引基本失效
验证方式很简单:SHOW INDEX FROM table_name 看 Cardinality,如果远低于表总行数(比如 100 万行,Cardinality 才 200),这个字段就不适合当复合索引首列。
LEFT JOIN 多列关联时,右表索引更容易被忽略
INNER JOIN 至少还可能触发索引合并(index_merge),但 LEFT JOIN 下,优化器对右表索引更保守——哪怕你建了完美匹配的复合索引,只要驱动表(左表)返回行数太多,右表索引也可能被跳过。
关键控制点:
- 左表必须有强过滤条件(比如
WHERE created_at > '2026-04-01'),让rows显著下降 - 右表的
ON字段必须全部落在同一复合索引的最左前缀上,不能分散在多个索引里 - 严禁在右表
ON条件中用函数,比如ON t1.code = UPPER(t2.code)→ 整个索引失效
执行时盯紧 EXPLAIN 的 Extra 列:如果出现 Using join buffer (Block Nested Loop),说明右表已退化为全表扫描,不是数据量问题,是索引没被识别或没生效。
别盲目建索引,先看 EXPLAIN 的 key 和 rows 是否真实匹配
很多人建完复合索引就以为万事大吉,结果 EXPLAIN 显示 key 是预期索引名,但 rows 还是几万甚至上百万——这说明索引虽被选中,但只用了前缀部分,后缀字段没参与查找。
典型误判场景:
- 索引是
(a, b, c),但ON条件只写了a = ? AND c = ?→c不会走索引 - 字段允许
NULL,旧版 MySQL 可能直接放弃使用索引(加IS NOT NULL过滤可缓解) - JOIN 字段类型不一致,比如一边是
VARCHAR(32),另一边是VARCHAR(64)→ 隐式转换导致索引失效
真正有效的验证,是对比 key_len:假设 user_id 占 8 字节、tenant_id 占 4 字节,复合索引命中两列时 key_len 应为 12;如果只有 8,说明第二列根本没用上。










