复合索引必须从最左列开始连续匹配才能生效,跳过最左列、字段类型或排序规则不一致、范围查询中断、误用FORCE INDEX等均会导致索引失效。

JOIN条件里没用复合索引最左列,索引直接失效
复合索引不是“只要字段都在条件里就能用”,它必须从定义时的最左列开始连续匹配。比如表 orders 上有索引 idx_user_status_created (user_id, status, created_at),但你在 JOIN 中只写 ON o.status = u.status,那这个索引完全不会被使用——EXPLAIN 里 type 会是 ALL,key 是 NULL。
原因很简单:B+ 树查找需要一个确定的起始位置,而跳过 user_id 就等于不知道从哪棵子树往下找。
- 必须确保 JOIN 条件中第一个被用到的索引列,就是该复合索引定义里的最左列
- 如果业务上真要按
status关联,要么补上user_id(如o.user_id = u.id AND o.status = 'paid'),要么单独为status建单列索引 - ON 子句字段顺序不影响优化器重排,但“是否包含最左列”不可绕过
关联字段类型或排序规则不一致,隐式转换让索引失效
哪怕 ON 条件满足最左前缀,只要两端字段类型或排序规则(COLLATION)不一致,MySQL 就会自动加 CAST() 或字符集转换,导致索引列实际被函数包裹,优化器只能弃用索引。
典型现象:EXPLAIN 显示 type=ALL、key=NULL,Extra 里出现 Using where; Using join buffer (Block Nested Loop)。
- 用
DESCRIBE orders和DESCRIBE users对比连接字段的类型、长度、字符集、COLLATION - 常见坑:一边是
VARCHAR+utf8mb4_0900_ai_ci,另一边是VARCHAR+utf8mb4_general_ci,也会失效 - 宁可改表结构(
ALTER TABLE ... MODIFY),也不要靠应用层加引号或CONVERT()来“凑合”
WHERE + ON 混合条件导致索引只用到部分列
复合索引能用多少,取决于等值条件是否从最左列开始连续出现。一旦中间插入范围查询(>、<、BETWEEN、LIKE 'abc%'),右侧所有列就只剩过滤作用,不再参与索引定位。
例如:索引是 (user_id, status, created_at),查询写成 WHERE user_id = 123 AND status IN ('paid','shipped') AND created_at > '2025-01-01',那索引最多用到前两列。
-
IN算等值条件,不会截断索引,但如果值太多(比如几百个),优化器可能主动放弃索引 - 如果查询常带时间范围,又高频查
status,应把status放在created_at左侧——但前提是status区分度足够高 - 用
EXPLAIN FORMAT=TRADITIONAL查看key_len,能直观看出用了索引的几字节(对应几列)
FORCE INDEX 对被驱动表无效,别乱加
FORCE INDEX 只能指定驱动表(通常是 FROM 后第一个表)用哪个索引,对被驱动表(ON 右侧那个)完全不起作用。强行加它,往往掩盖了真正问题。
比如 SELECT * FROM orders FORCE INDEX(idx_user_id) JOIN users ON orders.user_id = users.id,如果 users.id 没索引,这个 FORCE 就毫无意义,还可能让优化器选错路径。
- 先跑
EXPLAIN确认哪张表是驱动表、哪张是被驱动表 - 被驱动表的连接字段必须有索引——哪怕它是主键,也要确认
PRIMARY KEY真的存在且未被禁用 - 多列连接时,优先建复合索引(如
CREATE INDEX idx_user_status ON orders(user_id, status)),而不是多个单列索引
最常被忽略的一点:索引是否命中,和 SQL 写得“看起来对不对”关系不大,关键看执行计划里 key 和 key_len 的实际取值。哪怕你把所有字段都写进 WHERE,只要最左列没参与等值匹配,或者字段类型悄悄不一致,索引就等于不存在。

















