索引选择性差导致查询慢,因mysql误判索引价值或强制使用低效索引;需查cardinality、更新统计信息、避免索引列函数、结合过滤比与执行计划综合评估是否保留。

为什么 EXPLAIN 显示走了索引但查询还是慢
索引选择性差时,MySQL 会“误判”索引价值——比如对性别字段建了索引,cardinality 只有 2,优化器发现走索引要回表查 50% 的行,干脆全表扫描更省事。但有时它又“硬选”了,尤其当 WHERE 条件里混着多个字段、统计信息过期或用了函数时。
- 用
SHOW INDEX FROM table_name查Cardinality,低于总行数 1% 就算差(如千万级表,Cardinality ) - 执行
ANALYZE TABLE table_name强制更新统计信息,避免优化器依赖过期采样 - 别在索引列上用函数,
WHERE UPPER(name) = 'ABC'会让索引失效;改成WHERE name = 'abc'并确保 collation 支持大小写敏感
如何判断是否真该删掉低选择性索引
不是所有低基数索引都该删。有些场景下,哪怕只有两个值,它也能大幅减少扫描范围——比如 status ENUM('pending', 'done') 配合 ORDER BY created_at LIMIT 10,MySQL 可能用索引快速定位前 10 条 pending 记录,比扫全表快得多。
- 查
information_schema.STATISTICS看SEQ_IN_INDEX,确认该字段是否是复合索引最左前缀;如果不是,删掉影响小 - 用
SELECT COUNT(*) FROM t WHERE low_selectivity_col = 'x'估算实际过滤比例,> 15%通常不值得单独建索引 - 监控
Handler_read_next和Handler_read_rnd_next:后者飙升说明大量随机回表,正是低选择性索引拖累的典型表现
数据倾斜导致 GROUP BY 或 JOIN 卡死怎么办
当某一个 user_id 关联几百万条订单,而其他用户平均只关联几十条,MySQL 的哈希连接或分组聚合就会在单个线程上堆积大量数据,内存爆满、磁盘临时表膨胀、甚至触发 OOM killer。
- 加前置过滤:先用子查询把“坏数据”隔离出来,例如
SELECT * FROM orders WHERE user_id IN (SELECT user_id FROM users WHERE order_count - 改用
LEFT JOIN ... ON ... AND把倾斜键的条件下推到驱动表侧,避免被广播到所有分区 - 对极端倾斜键单独处理,比如把
user_id = 12345(超级用户)的逻辑拆成独立 SQL,其余走常规流程
OPTIMIZE TABLE 能不能救回选择性暴跌的索引
不能。索引选择性由数据分布决定,OPTIMIZE TABLE 只重建 B+ 树、合并碎片、更新统计信息,但不会改变字段本身的重复率。如果业务上就是大量重复值(如日志表的 service_name),再怎么优化也没用。
- 真正有效的动作是:换字段建索引(比如加时间前缀:
(service_name, create_time))、或者用覆盖索引避开回表 - 注意
innodb_stats_persistent必须为ON,否则ANALYZE TABLE的效果重启后丢失 - 如果表超大(>100GB),
ANALYZE TABLE可能锁表几分钟,建议在低峰期做,或设innodb_stats_auto_recalc = OFF后手动控制
低选择性问题本质是数据和索引设计的错配,不是靠命令能绕过去的。最常被忽略的是:没人在加索引前先跑一遍 SELECT COUNT(DISTINCT col)/COUNT(*) FROM t 算真实选择率。











