索引选择性低于0.05时基本无效,因优化器估算回表开销大于全表扫描,导致explain显示type=all或key=null;应通过count(distinct col)/count(*)验证,并结合执行计划、索引使用统计与删索引测试交叉确认。

怎么一眼看出索引选择性太差
直接查 COUNT(DISTINCT column) / COUNT(*),结果低于 0.05 就基本可以判死刑。比如 status 字段只有 '0' 和 '1',百万行数据里各占一半,算出来就是 2 / 1000000 = 0.000002 —— 这种索引 MySQL 很可能根本不用。
常见错误现象:EXPLAIN 显示 type=ALL 或 key=NULL,但你明明建了 INDEX idx_status (status);rows 接近表总行数,Extra 里没有 Using index;慢查询日志里反复出现同一类 WHERE status = ? 查询。
别信 CARDINALITY:用 SHOW INDEX FROM table_name 看到的 Cardinality 值可能严重失真,尤其在大表上没手动 ANALYZE TABLE 时。它只是采样估算,不是真实去重数。
为什么加了索引还是走全表扫描
MySQL 优化器不是“看到索引就用”,而是估算代价:走索引要先查索引树、再回表取数据,如果匹配行太多(比如查一半数据),回表开销反而比直接扫一遍还高。这时它会主动放弃索引,选 ALL。
典型触发条件:
-
WHERE条件字段重复值占比 > 95%(如is_deleted = 0) - 联合索引中低选择性字段放在后面,违反最左前缀(如建了
(created_at, status)却只查status) - 用了函数或类型转换,导致索引失效(
WHERE DATE(create_time) = '2026-01-01')
FORCE INDEX 强制走索引?别试。实测往往更慢,因为绕过了优化器的代价判断,硬拉一个低效路径。
怎么验证是不是选择性问题拖慢了查询
分三步交叉验证,缺一不可:
① 查真实使用情况:SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, COUNT_READ FROM performance_schema.table_io_waits_summary_by_index_usage WHERE INDEX_NAME = 'idx_status' AND COUNT_READ = 0; —— 如果 COUNT_READ 一直是 0,说明这个索引线上根本没人用。
② 对比执行计划:EXPLAIN SELECT id, status FROM t WHERE status = 1; 和 EXPLAIN SELECT * FROM t WHERE status = 1;。前者如果显示 type=ref + Extra=Using index,后者却是 type=ALL,就坐实是回表开销过大导致放弃索引。
③ 模拟删索引测试:DROP INDEX idx_status ON t; 后跑慢查询,如果 EXPLAIN 结果没变、执行时间也没明显波动,那这索引本来就是个摆设。
删之前必须确认的三件事
低选择性索引不是“不好”,而是“不该存在”。但它可能被隐式依赖:
- 检查是否被
FOREIGN KEY或UNIQUE约束绑定:SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_NAME = 't' AND CONSTRAINT_SCHEMA = 'db'; - 确认没被
ORDER BY或GROUP BY依赖:哪怕查询没WHERE,也可能靠它避免Using filesort - 查慢日志里最近 7 天是否真有这条索引参与的
JOIN或ORDER BY场景(不只是WHERE)
真正容易被忽略的点:应用层缓存可能依赖这个索引支撑的统计查询(比如 SELECT COUNT(*) FROM t WHERE status = 1)。删之前得确认这类结果是否已转由汇总表或 Redis 缓存兜底。











