索引失效的常见场景:为什么没用上索引
SilverLens
6 分钟阅读
判断先说:SQL 写得"看起来能走索引",优化器却选了全表扫描,几乎都源于对索引结构和类型转换规则的误解。索引失效不是玄学,逐条对照执行计划(EXPLAIN)就能定位。大多数"表才几万行就慢"的工单,查到底都是某个隐式转换或函数运算让索引悄悄失效了。
用 EXPLAIN 看真实选择
不要凭直觉猜。执行计划的 type 列、key 列、rows 列最说明问题:
EXPLAIN SELECT * FROM orders WHERE DATE(create_time) = '2026-10-22';
-- key 为 NULL、type 为 ALL,说明没用上索引
type 从优到劣大致是 const > ref > range > index > ALL;key 为 NULL 表示实际没选任何索引;rows 是估算扫描行数,与真实行数差距巨大时,往往是统计信息或类型转换在作怪。养成习惯:任何慢查询先 EXPLAIN 看 key 是不是预期的那一列,比反复调 SQL 写法更靠谱。
常见失效场景
对字段做函数运算或表达式计算。索引存的是字段原值,对字段套函数后,原值已被变换,B+ 树无法按变换后的值定位,只能全表把每行算一遍再比较。
-- 失效:在列上套了函数
WHERE DATE(create_time) = '2026-10-22'
-- 改写后可命中范围索引
WHERE create_time >= '2026-10-22 00:00:00'
AND create_time < '2026-10-23 00:00:00'
同样的坑还有 WHERE amount * 1.1 > 100、WHERE YEAR(created_at) = 2026 这类,只要在列上做了运算,索引基本就废了。解决思路是"把运算从列上挪走",改成对常量运算、对列做范围。
隐式类型转换。字符串列传入数字、或数字列传入字符串,MySQL 会按规则把其中一方转换后再比较,导致列上的索引失效。这个尤其隐蔽,因为 SQL 能正常执行、不报错,只是慢。
-- phone 是 VARCHAR,传入数字会触发转换
WHERE phone = 13800000000; -- 失效
WHERE phone = '13800000000'; -- 命中
经验上,凡是 EXPLAIN 显示 key 为 NULL 而你认为应该命中时,第一反应是检查"传入参数类型"和"列类型"是否一致,十有八九命中问题。
隐式字符集或排序规则不一致。两表关联时,如果关联字段的字符集或 collation 不同,会触发逐行转换,索引在关联时不生效。这类问题在"老表用 utf8、新表用 utf8mb4"的混合环境里最常见,表现就是 JOIN 巨慢但单表查很快。统一字符集和排序规则,比加索引更治本。
前导模糊匹配。以通配符开头的 LIKE 无法利用 B+ 树的有序性,因为不知道从哪个位置开始。
WHERE name LIKE '%明'; -- 失效,只能全扫描
WHERE name LIKE '张%'; -- 可命中,前缀有序
如果业务确实需要"任意位置包含"的搜索,前缀 LIKE 救不了,得往倒排索引或专用搜索引擎方向走,不要指望 B+ 树索引能扛后缀、中缀匹配。
不符合最左前缀。联合索引 (a, b, c) 只有在查询条件包含 a(或 a,b)时才有效;跳过了最左列 a,直接用 b 或 c 查,索引用不上。范围查询之后的列也会失效:WHERE a=1 AND b>10 AND c=2 里,c 无法用上索引,因为 b 是范围后,索引的有序性到 b 就断了。
OR 连接了无索引列。只要 OR 的一侧列没有索引,优化器往往放弃使用另一侧本可命中的索引,改为全表扫描。可拆成 UNION 或给另一侧补索引。曾见过一个 WHERE a=1 OR b=2,a 有索引 b 没有,结果整句全表扫描,补上 b 的索引后立刻好转。
优化器认为全表更快。当过滤后的结果集占表的大部分(经验上超过约 20%–30%,具体阈值由统计信息决定),回表代价高,优化器会主动放弃索引。这不是 bug,是代价模型的选择。此时该怀疑的是"是不是该缩小查询范围、分页、或改读写分离",而非"为什么不用索引"。
复合索引的顺序怎么定
把区分度高、且常出现在等值条件里的列放前面;范围条件列置于末尾。区分度低、动辄命中半数数据的列(如性别、状态枚举),单独建索引意义不大,放联合索引头部还会拖慢选择性。一个实用判断:把"用得最多、过滤最强"的列放最左,让大多数查询都能命中前缀。
索引建完不是终点,统计信息会随数据变化而过期。当执行计划突然变差,先 ANALYZE TABLE 刷新统计信息,再判断是不是真的需要新建或调整索引。很多时候"莫名其妙变慢"只是统计信息 stale 了,刷新一下就恢复,不必急着加索引。
怎么系统地发现索引问题
与其等慢查询来了再排查,不如主动监控。慢查询日志是最直接的入口,把 long_query_time 设小一点,把 log_queries_not_using_indexes 打开,就能捕获那些本该走索引却全表扫描的语句。再配合分析慢日志的工具,能列出"建了却从来没被用到的索引",这些冗余索引不仅占空间,还拖慢写入和备份。经验上,定期清理无用索引和补充缺失索引,是性价比最高的数据库优化动作,比调参数立竿见影。索引健康度也该纳入日常巡检,而不是等到性能投诉才介入。
浏览 0
点赞 0
评论 0