索引明明建了却没用上:几种最常见的索引失效场景
索引明明建了却没用上:几种最常见的索引失效场景
「这个字段我加索引了啊,怎么还是全表扫描?」这是慢查询排查里出现频率最高的一句话。索引失效很少是因为数据库"忘了"用索引,绝大多数时候是查询写法、字段类型或统计信息让优化器判断"走索引更贵"。把下面几类场景过一遍,能解决大部分问题。
一、在索引列上做运算或函数
最典型的一类。where date(created_at) = '2026-10-01' 这种写法,索引是按 created_at 的原始值排的,一旦套上函数,优化器就无法用索引定位,只能逐行算。
正确的做法是把函数挪到常量侧:写成范围条件 created_at >= '2026-10-01 00:00:00' and created_at < '2026-10-02 00:00:00'。同理,where id + 1 = 100 改成 where id = 99,where substr(name,1,3) = 'abc' 改成前缀匹配 name like 'abc%'。
like 的规则也常被记错:'abc%' 能用索引,'%abc' 和 '%abc%' 不能——因为前者有确定的前缀,后者只能全扫。真要做全文检索,应该上倒排索引或搜索引擎,别指望 B+ 树。
二、隐式类型转换
这一条最隐蔽,因为它不报错、不提示。假设手机号字段是 varchar,而查询里传进去的是数字(很多 ORM 或参数绑定会悄悄把纯数字字符串转成整数),数据库就会按「字符串转数字」的规则比较,等价于对每一行做一次 cast(phone as signed),索引直接作废。
反过来,如果字段是数字类型,而传进来的是字符串,通常还能用索引,因为常量侧的转换不破坏列的有序性。所以判断依据是:转换发生在列上还是常量上。发生在列上就废。
字符集不一致也会造成同样的效果。两张表关联时一个用 utf8、一个用 utf8mb4,on 条件里的列无法直接比较,需要隐式转换,索引照样失效。这类问题在分库分表之后特别常见。
三、违反最左前缀
联合索引 (a, b, c) 是按 a 再 b 再 c 排序的。所以:
where a = ?能用where a = ? and b = ?能用where a = ? and c = ?只能用上a这一段where b = ?完全用不上
范围条件会截断后续列:where a = ? and b > ? and c = ?,c 就用不到索引了(部分版本能做索引条件下推,但那是过滤优化,不是定位)。
设计联合索引时,把等值条件多、区分度高的列放前面,范围查询的列放最后,这条经验能覆盖八成场景。
四、区分度太低
如果某个字段只有两三个取值(比如性别、是否删除),优化器会算一下:走索引要回表取大量行,不如直接全表扫。这不是 bug,是成本估算的结果。
遇到这种情况,正确方向是建联合索引把区分度带起来,或者干脆用覆盖索引避免回表。单纯给低区分度字段加单列索引,收益接近于零。
五、统计信息过期
优化器靠统计信息估算行数。如果表刚经历大批量写入或删除,统计信息没更新,估算就会严重偏离实际,可能选错执行计划——包括放着索引不用。
定期更新统计信息,以及在计划突然变差时第一时间怀疑它,能省很多排查时间。另外要注意:同一个 SQL 在不同数据量下选不同计划是正常的,不要看到执行计划变了就认为出了问题。
六、把索引"藏"起来了
几个容易被忽略的细节:
or 连接的条件里只要有一侧没有索引,整体就可能退化成全表扫。改成 union all 往往更好。
!=、not in、is not null 这类否定条件,通常无法有效利用索引。
排序方向不一致时,联合索引可能无法同时满足 order by。比如索引是 (a asc, b asc),查询写 order by a asc, b desc,就需要额外排序。
一个实用的排查顺序
拿到慢 SQL,先看 explain:type 是不是 all、key 是不是 null、rows 估算多大、extra 里有没有 using filesort 或 using temporary。然后按顺序核对:字段类型是否匹配、条件里有没有函数或运算、联合索引的最左列有没有用上、区分度是否足够、统计信息是否新鲜。
绝大多数"索引失效"都能在这五步内定位。真正需要重构索引设计的情况,其实比想象中少。
- 点赞
- 收藏
- 关注作者
评论(0)