真正决定是否走复合索引的是EXPLAIN中type为ref/eq_ref/range且key显示预期索引名;key为NULL或type为ALL/index则未走索引,需同时检查驱动表rows和被驱动表key_len。

看EXPLAIN的type和key字段是否匹配预期
真正决定“有没有走复合索引”的,不是你建了什么索引,而是EXPLAIN里type是不是ref、eq_ref或range,且key显示的是你期望的那个复合索引名。如果key是NULL,或者type是ALL或index,那基本没走——哪怕索引存在、字段也全对上了。
常见误判点:
- 只盯着
possible_keys有值就以为索引用上了,其实它只是候选列表,key才是最终拍板的 -
key_len太小:比如你建的是(user_id, tenant_id),但key_len只显示user_id的长度(如4字节),说明tenant_id没被用上,可能因为前面字段没做等值过滤,或顺序不匹配 -
Extra里出现Using join buffer (Block Nested Loop):这是明确信号——被驱动表退化成全表扫描了,复合索引完全失效
验证复合索引字段是否按最左前缀完整参与
复合索引不是“建了就能用”,必须满足最左前缀原则在JOIN和WHERE中同时生效。例如ON子句是o.user_id = u.id AND o.tenant_id = u.tenant_id,而你给users表建了(id, tenant_id),那就对;但如果建的是(tenant_id, id),MySQL大概率只用上tenant_id,id会被跳过——因为ON里没有u.tenant_id = ?这种独立等值条件来激活首列。
实操建议:
- 把ON中所有等值字段,按实际出现顺序(非逻辑重要性)放进复合索引最左部分
- 如果还有WHERE条件,比如
WHERE u.status = 'active',且这个字段高选择性,就把它放在等值字段之后、范围字段之前,比如(id, status, tenant_id) - 用
SHOW INDEX FROM users确认索引定义,别信名字——idx_user_tenant可能实际建在order_id上
注意驱动表筛选性对复合索引生效的决定性影响
即使右表索引完美,只要左表(驱动表)返回行数太多,优化器也可能放弃使用右表索引。比如LEFT JOIN中,orders为左表且没加任何WHERE,100万行全扫一遍,每行再去users表查一次,哪怕users.id有索引,总开销也远超直接走users主键+回表。
关键检查点:
- 驱动表的
rows值是否明显小于表总行数?如果不是,说明统计信息过期,运行ANALYZE TABLE orders - 驱动表是否有强过滤条件?比如
WHERE created_at > '2026-10-01'比WHERE status IN ('a','b','c')更容易触发索引下推 - 避免在驱动表JOIN字段上用函数:
ON DATE(o.created_at) = u.login_date会让o.created_at索引失效,改用范围写法
字符集、类型、隐式转换会让复合索引彻底掉线
两个字段值相等,不等于它们能一起走索引。一旦发生隐式类型转换或字符集不一致,B+树就无法定位,复合索引形同虚设。
典型场景:
-
orders.user_id是INT,users.id是BIGINT→ MySQL可能放弃任一索引 -
orders.code是VARCHAR(32) utf8mb4,products.code是VARCHAR(32) utf8→ 字符集不同,无法走索引 -
ON t1.name = UPPER(t2.name)→ 函数作用于t2.name,索引失效 - 外键字段没手动建索引 →
ALTER TABLE加约束不会自动创建索引,必须CREATE INDEX显式补上
复合索引在JOIN中是否生效,从来不是“有没有”的问题,而是“用没用上、用上多少、用得稳不稳”的连续判断。最容易被忽略的是:驱动表的rows值和被驱动表的key_len必须同时达标,缺一不可。











