mysql索引选择性低怎么办_mysql优化解决方案

来源:这里教程网 时间:2026-02-28 20:47:26 作者:

为什么
SELECT COUNT(*)
走了索引却还是慢?——选择性低的索引本质问题

索引选择性低,不是“建了没用”,而是“用了也白用”。核心在于:MySQL 优化器发现走这个索引后仍需回表大量数据,不如直接全表扫描。典型场景是给

status
is_deleted
gender
这类只有几个取值的字段建了单列索引。即使
EXPLAIN
显示
type=ref
range
,实际执行时也可能因
rows
预估过大而放弃使用,或虽用了但 I/O 开销不降反升。

判断索引是否真被用上:别只看
EXPLAIN
,要查
Handler_read_*

优化器决策依赖统计信息,而

EXPLAIN
只是预估。真正验证是否走索引,得看运行时的存储引擎计数器:

SHOW STATUS LIKE 'Handler_read%';

重点观察:

Handler_read_key
(索引查找次数)和
Handler_read_next
(索引顺序扫描次数)是否明显上升;同时对比
Handler_read_rnd_next
(回表随机读次数)是否暴增——如果后者远高于前者,说明索引虽被选中,但导致大量回表,性能反而恶化。

执行查询前先
FLUSH STATUS
再跑一次目标 SQL 立刻查
Handler_read_*
,比看
EXPLAIN
key
字段更可靠

提升选择性的实操路径:不是删索引,而是换建法

对低选择性字段,强行加单列索引基本无效。有效做法是把它作为复合索引的「后缀」,而非前导列:

错误写法:
INDEX idx_status (status)
→ 几乎无用
正确思路:找出常和
status
一起过滤的高选择性字段,比如
created_at
user_id
,构建
INDEX idx_user_status (user_id, status)
INDEX idx_time_status (created_at, status)
注意顺序:
WHERE user_id = ? AND status = ?
必须把高选择性列放前面,否则索引无法命中前缀
若查询含
ORDER BY status
,且
status
是唯一排序字段,那复合索引也救不了——此时应考虑归档冷数据或改用物化视图/汇总表

替代方案:覆盖索引 + 条件过滤,绕过低选择性瓶颈

当查询只涉及少量字段,且其中包含低选择性列时,优先走覆盖索引,彻底避免回表:

SELECT id, status, updated_at FROM orders WHERE status = 'pending' ORDER BY updated_at DESC LIMIT 20;

对应索引应为:

INDEX idx_cover (status, updated_at, id)
。这样 MySQL 可在索引 B+ 树里完成全部查找、排序、取值,
Handler_read_rnd_next = 0

但要注意:覆盖索引会增大索引体积,写入开销上升。如果

status
更新频繁,且该索引又很宽,可能引发页分裂和缓冲池压力。上线前务必在压测环境对比
innodb_buffer_pool_reads
innodb_pages_written
的变化。

低选择性本身不可怕,可怕的是把它当成独立优化点去硬扛。它往往是业务模型或数据分布的信号——比如

status IN ('pending', 'processing')
返回 95% 的行,那问题不在索引,而在是否真需要实时查这 95%。

相关推荐