索引选择性到底影响什么
索引选择性低(比如
gender字段只有 'M'/'F' 两种值),MySQL 很可能直接放弃走索引,改用全表扫描。这不是 MySQL “偷懒”,而是因为回表成本太高:查 10 万行索引后,再随机读 10 万次磁盘取完整行,比顺序扫一遍聚簇索引还慢。
选择性计算公式是:
COUNT(DISTINCT column) / COUNT(*)。> 0.2 通常算不错;FORCE INDEX,但那往往是掩盖更深层设计问题。 日期字段如
created_at选择性通常高,但加了范围查询(
BETWEEN)后,实际有效选择性会随时间推移下降 复合索引的最左前缀列选择性必须足够,否则整个索引都容易被跳过
EXPLAIN中的
key列为空,
type是
ALL,基本就是选择性失效的信号
为什么 LIKE '%abc'
让索引完全失效
前导通配符破坏了 B+ 树的有序遍历能力。B+ 树只能从左到右匹配前缀,
'%abc'意味着“结尾是 abc”,数据库无法定位起始页,只能扫全索引甚至全表。
对比来看:
LIKE 'abc%'可以用索引,
LIKE 'ab%c'也能用(匹配到
'ab'后再过滤),但只要开头是
%或
_,优化器就直接排除该索引。 想模糊搜结尾?考虑倒序存储 +
LIKE 'cba%',或上
FULLTEXT索引(仅限
MyISAM/
InnoDB的全文索引支持)
INSTR(column, 'abc') > 0、
LOCATE('abc', column) > 0 同样无法走索引,本质一样
如果业务真需要高频后缀搜索,GENERATED COLUMN + STORED + INDEX是更可控的选择
ORDER BY
和 WHERE
共用索引时的优先级陷阱
MySQL 不会为
WHERE和
ORDER BY分别选两个索引。它只挑一个索引,要么加速过滤,要么加速排序——除非这个索引能同时覆盖两者。
例如表有
(status, created_at)复合索引,查询
WHERE status = 'active' ORDER BY created_at DESC就能高效走索引;但如果写成
WHERE status IN ('active', 'pending') ORDER BY created_at,IN导致范围扩大,
ORDER BY部分大概率失效,执行计划里会出现
Using filesort。
WHERE条件中含
!=、
NOT IN、
OR(未被重写为
UNION)时,复合索引的排序能力基本归零
ORDER BY字段类型要和索引定义一致:索引是
INT,但
ORDER BY CAST(x AS CHAR)会丢索引 覆盖索引(
SELECT字段全在索引中)能避免回表,但不解决
filesort问题;得靠索引结构本身支撑排序逻辑
查看真实选择性与索引使用情况的实操命令
别只信
SHOW INDEX,它不告诉你数据分布。用下面这条查实际选择性:
SELECT COUNT(DISTINCT status) / COUNT(*) AS selectivity, COUNT(*) AS total_rows FROM orders;
再结合
EXPLAIN FORMAT=JSON看优化器决策依据,重点关注
used_key_parts和
rows字段。如果
rows远大于结果集数量,说明索引没起到预期过滤作用。
临时关闭索引统计(用于测试)可用:
SET SESSION optimizer_switch = 'use_index_extensions=off';,但生产慎用——这只是验证手段,不是调优方案。
真正难的不是建索引,是判断「这个查询是否值得为它付出写放大和锁开销」。很多看似慢的查询,加索引后反而拖垮写入吞吐,尤其是高频更新的小字段。
