低选择性索引会拖慢查询,因其重复值过多导致优化器放弃使用而改用全表扫描;索引选择性低于0.05时通常无效,且会增加写入开销和内存占用,应优先考虑覆盖索引、分区或汇总表等替代方案。

为什么低选择性索引反而拖慢查询
当 WHERE 条件字段的重复值过多(比如 status 只有 '0'/'1',或 gender 只有 'M'/'F'),MySQL 很可能直接放弃走索引,改用全表扫描——因为走索引要回表多次,成本比扫一遍还高。
典型表现:EXPLAIN 中 type 是 ALL 或 index,key 为空,rows 接近表总行数。
- 索引选择性 =
COUNT(DISTINCT column) / COUNT(*);低于 0.05 通常视为低选择性 - 即使加了索引,优化器也可能忽略它——这不是 bug,是成本估算结果
-
FORCE INDEX强制走索引往往更慢,别试
用覆盖索引绕过回表开销
如果查询只涉及少量字段,且这些字段都在索引里,MySQL 就不用回主键查找数据行。这对低选择性字段特别有效。
例如:查询 SELECT id, status FROM orders WHERE status = 1,可建联合索引:ALTER TABLE orders ADD INDEX idx_status_id (status, id)。
- 顺序很重要:低选择性字段放前面(
status),高选择性字段放后面(id) - 避免
SELECT *,只查真正需要的列 - 覆盖索引对
ORDER BY和LIMIT也友好,能避免 filesort
用分区或物化视图替代单字段索引
对固定取值、高频过滤但低选择性的字段(如 tenant_id、region),单列索引意义不大,可考虑按该字段做范围/列表分区。
MySQL 8.0+ 支持不可见索引和降序索引,但解决不了根本问题;真正有效的思路是把“筛选逻辑”前置。
- 按时间 + 状态组合分区:
PARTITION BY LIST COLUMNS(status) ...,让引擎快速跳过无关分区 - 用汇总表替代实时查:
CREATE TABLE orders_summary AS SELECT status, COUNT(*) cnt FROM orders GROUP BY status - 应用层缓存状态统计类结果,比反复查低选择性字段更可靠
什么时候该删掉这个索引
低选择性索引不仅没用,还会拖慢写入(每次 INSERT/UPDATE 都要维护索引树)、增大内存占用、干扰优化器判断。
运行 SELECT * FROM sys.schema_unused_indexes(需启用 performance_schema),或查 information_schema.STATISTICS 配合慢日志分析实际命中率。
- 连续一周没有被
WHERE、JOIN、ORDER BY使用过的索引,大概率可以删 - 唯一约束或外键依赖的索引不能删,哪怕选择性低
- 删除前先用
DROP INDEX idx_name ON tbl_name;测试,观察慢查询是否回升
低选择性本身不是病,硬给它配索引才是。关键是看查询模式是否真需要靠它驱动,还是只是惯性加索引。










