低选择性字段单独建索引通常无效,但联合索引中合理前置、覆盖查询或数据极度倾斜时仍有用;判断索引是否生效需看key_len和rows而非仅type=ALL;冗余索引应通过工具或SQL识别后谨慎删除。

低选择性字段加索引到底有没有用
绝大多数情况下,WHERE 条件里对低选择性字段(比如 status 只有 0/1,或 gender 只有 'M'/'F')单独建索引,基本没用——MySQL 优化器大概率会直接走全表扫描,因为回表成本可能比扫一遍还高。
但“没用”不是绝对的,关键看场景:
- 该字段常和其他高选择性字段组合查询,且索引顺序合理(如
(status, create_time),create_time是高选择性时间字段) - 查询只查索引覆盖列(
SELECT status FROM t WHERE status = 1),能走index-only scan - 表极大(亿级),且该字段值分布极不均匀(比如 99.9% 是 0,只有 0.1% 是 1),而你总查那 0.1%,这时
status = 1可能触发索引使用
EXPLAIN 显示 type=ALL 却建了索引?先看 key_len 和 rows
别光看 type 是 ALL 就断定索引失效。重点看 key_len 是否非 0、rows 是否显著小于表总行数:
-
key_len > 0说明索引被部分使用(比如联合索引前缀匹配) -
rows如果是几千而不是几百万,说明 MySQL 认为这个索引筛选效率尚可,只是没选它做驱动表 - 真正无效的典型表现是:
key_len = 0且key = NULL
示例:EXPLAIN SELECT * FROM order WHERE status = 1 AND user_id = 12345; 若 user_id 上有独立索引,而 (status, user_id) 是联合索引,MySQL 很可能用后者,key_len 显示用了 2 个字段长度,哪怕 status 本身低选择性。
联合索引中低选择性字段放前面还是后面
放前面 ≠ 浪费,但放错位置会导致高选择性字段无法生效。核心原则:最常用于等值查询(= 或 IN)的字段靠左,范围查询(>, BETWEEN, LIKE 'abc%')字段靠右,且只能有一个范围字段在最后。
- ✅ 推荐:
(status, user_id, create_time)→ 支持WHERE status = 1 AND user_id = ?,也支持WHERE status = 1 AND user_id = ? AND create_time > ? - ❌ 避免:
(user_id, status, create_time)→WHERE status = 1单独查时完全用不上;WHERE user_id = ? AND status = 1虽能用,但status在第二位,对排序/分页帮助有限 - ⚠️ 注意:
status IN (0,1)在联合索引中算等值条件,不是范围,所以仍可继续用后续字段
用 pt-duplicate-key-checker 或 INFORMATION_SCHEMA 查冗余索引
低选择性索引常和高选择性索引重叠,形成冗余。比如已有 (user_id, status),再单独建 status 索引就是浪费。
查冗余推荐两法:
- 用
pt-duplicate-key-checker --host=localhost(Percona Toolkit),它会明确标出哪些索引被另一个“包含” - 手动查:
SELECT s1.TABLE_NAME, s1.INDEX_NAME AS redundant_index, s2.INDEX_NAME AS dominant_index FROM INFORMATION_SCHEMA.STATISTICS s1 JOIN INFORMATION_SCHEMA.STATISTICS s2 ON s1.TABLE_SCHEMA = s2.TABLE_SCHEMA AND s1.TABLE_NAME = s2.TABLE_NAME AND s1.SEQ_IN_INDEX < s2.SEQ_IN_INDEX AND s1.COLUMN_NAME = s2.COLUMN_NAME WHERE s1.TABLE_SCHEMA = 'your_db';再人工比对前缀是否一致
删之前务必确认慢查日志里真没语句依赖那个单列索引——有些 ORM 自动生成的 WHERE status = ? 查询,看着简单,删了就变全表扫。










