索引未被使用主因是字段类型或字符集不一致导致隐式转换,或on条件写法错误;left join右表必须为关联字段建独立索引;straight_join不保证索引生效,需配合正确索引与统计信息更新。

EXPLAIN看到type=ALL但关联字段明明有索引
这说明索引建了,但没被用上——不是MySQL“不认”,而是你写的SQL或表结构让优化器主动绕开了它。最常见原因不是漏建索引,而是索引建得不对、用法写错了,或者类型/字符集不一致触发了隐式转换。
常见错误现象:
-
EXPLAIN里key列为NULL,但possible_keys显示有索引名 - 左表10万行,右表5万行,加了
INDEX(user_id)后执行时间仍超10秒 - 把
orders.user_id改成INT后突然快了,之前是BIGINT
实操建议:
- 检查
ON两侧字段类型是否严格一致:比如users.id是BIGINT UNSIGNED,orders.user_id必须也是BIGINT UNSIGNED,差一个UNSIGNED都可能触发转换 - 确认字符集和校对规则完全相同:
SHOW FULL COLUMNS FROM users和SHOW FULL COLUMNS FROM orders对比Collation列,utf8mb4_0900_ai_ci和utf8mb4_general_ci不兼容 - 避免在
ON里用函数:ON UPPER(u.email) = UPPER(o.email)会让两边索引全失效;应统一存小写,查时直接用= - 复合索引只对最左前缀生效:
INDEX(status, user_id)不能加速ON o.user_id = u.id,除非查询同时带WHERE status = ?
LEFT JOIN右表索引建了还是全表扫描
LEFT JOIN的右表(被驱动表)哪怕只有一行不匹配,MySQL也得为左表每一行去右表找一遍匹配——没索引就等于每行都扫右表全量。很多人误以为“左表是主表,右表索引不重要”,这是最大误区。
实操建议:
- 对
LEFT JOIN右表的关联字段必须单独建索引,不能依赖联合索引的非最左字段。例如ON u.id = o.user_id,orders.user_id必须有独立索引或作为联合索引最左列 - 别在
LEFT JOIN后加WHERE o.id IS NULL再不建索引——这会让MySQL先硬扫右表生成临时结果,再过滤,rows值直接等于右表总行数 - 如果右表是字典类小表(如
status表),且左表极大,考虑改用INNER JOIN+UNION ALL补NULL逻辑,反而更快
STRAIGHT_JOIN用了还是慢,索引没起作用
STRAIGHT_JOIN只是强制驱动表顺序,并不保证索引被用上。如果你指定左表为驱动表,但右表关联字段没索引,或类型不一致,照样type=ALL。
实操建议:
- 用
EXPLAIN FORMAT=TREE(MySQL 8.0+)确认实际驱动路径,别只信STRAIGHT_JOIN写了就生效 - 强制顺序前先确保被驱动表的
ON字段有可用索引,且EXPLAIN中key列已显示该索引名 -
STRAIGHT_JOIN是临时止痛药,不是长期方案。一旦表数据分布变化(比如某状态用户从1%涨到40%),原来最优顺序可能变最差 - 定期运行
ANALYZE TABLE更新统计信息,否则优化器即使看到索引,也可能因行数预估严重偏差而放弃使用
为什么覆盖索引对驱动表无效
覆盖索引(Using index)只能减少被驱动表的回表开销,因为驱动表本身就要全表扫描取行数据,它的索引只用于定位,无法跳过读主键聚簇索引的过程。
实操建议:
- 想让驱动表也走覆盖索引?不行。但可以缩小它的扫描范围:比如
WHERE users.status = 'active'配合INDEX(status, id),让驱动表只扫活跃用户,而不是全表 - 被驱动表的覆盖索引要包含
SELECT所有字段 +ON字段,例如SELECT u.name, o.amount FROM users u JOIN orders o ON u.id = o.user_id,则orders上建INDEX(user_id, amount)才有效 -
TEXT/BLOB字段不能进索引,相关查询字段无法参与覆盖,这类场景尽早考虑冗余字段或应用层拼装
最容易被忽略的是字段类型和字符集的一致性——看着索引建了,EXPLAIN里却压根不显示,连报错都没有,只默默退化成全表扫描。










