题目:MySQL 中索引失效的问题如何排查?
难度:medium
标签:db/mysql/index, mysql/index, difficulty/medium
评分:⭐⭐☆☆(2/5)
索引失效,SQL,MySQL 索引失效。失效的话,首先你要加了索引,对吧?MySQL 里边这个执行的 SQL 语句,使用的查询记录中使用的索引的字段。然后我们看一下执行计划,并没有走这个索引字段。然后我们我们分析可能的情况。首先走索引需要它目标值匹配,也就是说你使用的查询方式跟你所走的索引能匹配的上。首先是字段类型,如果是类似于 char 跟 int 类型去匹配,这时候可能就会没有走你对应的设置的索引。另外就是是否加了特殊的处理,比如说字段上用了计算值,比如说成本,你比较的时候使用了加几,在成本的字段上加了多少,然后去算一个值。另外就是你做了一些特殊处理,比如截取,最终导致加了一个函数,导致你的对你的设置索引的字段出现了特殊处理,使得它不属于核心。另外就是可能说你使用了索引,但是底侧你使用了非索引字段去排序,最终致它觉得优化器觉得你这使用的索引不合理,或者是说成本太高了,反而它去直接使用全表而不走你的索引,这时候可能要做联合索引去处理相关的问题。
优点:
不足:
EXPLAIN FORMAT=JSON 的 missing_covering_index / attached_condition、sys.schema_index_statistics 的 rows_selected/rows_examined 比、performance_schema.table_io_waits_summary_by_index_usageLIKE '%xx' 左模糊、OR 拼接非索引列、NOT IN、!=、< >、IS NOT NULLORDER BY 与 GROUP BY 走的是不同索引时的 filesortcost、rows、key_len)ANALYZE TABLE 更新统计信息、FORCE INDEX 强制走索引、optimizer_switch 关闭 index_merge 等兜底手段EXPLAIN ANALYZE 真实执行代价补充:
EXPLAIN 看 key=NULL → EXPLAIN FORMAT=JSON 看 cost_info 与 missing_covering_index → SHOW INDEX 确认索引还在< > / IS NULL / ORDER BY 非索引列 / charset 不一致ANALYZE TABLE 刷新统计 → FORCE INDEX 硬推 → 关闭 index_merge 观察 → 拆分 SQL 到合适索引核心考点定位:掌握一套从"发现未走索引"到"定位失效原因"再到"给出修复方案"的完整排查链。
知识点拆解:
SHOW INDEX FROM t 看索引列、Cardinality 基数是否正常、Non_unique 是否唯一。索引不存在或列写错是最基础的可能。EXPLAIN 看 key 列是否为 NULL、rows 是否等于全表、Extra 是否有 Using filesort / Using temporaryEXPLAIN FORMAT=JSON 看 missing_covering_index 建议、cost_info.query_costsys.schema_index_statistics:rows_selected / rows_examined 长期低于 1% 说明索引扫描了大量无用行performance_schema.table_io_waits_summary_by_index_usage:索引长期 count_star=0 说明这个索引根本没人用EXPLAIN ANALYZE 看真实执行代价WHERE varchar_col = 123),MySQL 会强制把 varchar 转 number,索引失效WHERE YEAR(create_time)=2024、WHERE UPPER(name)='A'、WHERE age+1=10WHERE name LIKE '%ab',前缀不确定无法用 B+ 树有序性WHERE a=1 OR non_index=2 退化为全表NOT IN、!=、< >、IS NOT NULL:范围或反向判断,优化器倾向全表ORDER BY / GROUP BY 非索引列:引发 filesort,若排序后需要大量回表,优化器可能放弃索引WHERE FIND_IN_SET('x', col)、col LIKE '%a%' 全失效IN 元素过多:几百个值优化器倾向全表(受 eq_range_index_dive_limit 影响)YEAR(t)=2024 → t >= '2024-01-01' AND t < '2025-01-01')ANALYZE TABLE 刷新统计信息、FORCE INDEX 硬推、临时 optimizer_switch 关闭 index_merge考察方向:
延伸递进面试题:
EXPLAIN 显示的 rows 可能比实际扫描行数大或小很多?rows 是基于统计信息的估算,ANALYZE TABLE 后会更准;8.0 用 EXPLAIN ANALYZE 看真实值。IN (1,2,...,500) 里的 500 个值为什么不走索引?eq_range_index_dive_limit(默认 200)限制,超过阈值优化器用 statistics 估算而不是精确 index dive,估算偏大导致选全表。可以 SET eq_range_index_dive_limit=1000。sys.statement_analysis 里怎么定位长期不走的索引?schema_index_statistics 看 count_star=0 且 rows_examined>0 的索引,是候选可删除索引;再结合 EXPLAIN 验证是否真的没人用。面试官您好,索引失效排查我一般分三步走。
第一步是确认现象。跑一下 EXPLAIN,重点看三个字段:key 是不是 NULL,如果是就是没走索引;rows 是不是接近全表;Extra 里有没有 Using filesort 或 Using temporary。如果不确定,可以再跑 EXPLAIN FORMAT=JSON,里面会有 missing_covering_index 建议和 cost_info 的估算代价,MySQL 8.0 还有 EXPLAIN ANALYZE 能看真实执行耗时。
第二步是定位原因,按几个高频场景去排查。第一类是隐式转换,比如 varchar 列跟数字比较,WHERE code=123 而不是 WHERE code='123',MySQL 会把 varchar 强制转 number,索引就废了。第二类是字段上加了函数或运算,比如 WHERE YEAR(create_time)=2024、WHERE UPPER(name)='A'、WHERE age+1=10,这些都会让索引失效,标准改法是反向写成范围条件 create_time >= '2024-01-01' AND create_time < '2025-01-01'。第三类是查询模式问题,LIKE 左模糊、OR 拼接非索引列、NOT IN、!=、< >、IS NOT NULL,这些都会让优化器倾向全表。第四类是排序或分组问题,ORDER BY 一个非索引列会触发 filesort,如果文件排序代价高,优化器会放弃索引走全表。第五类是统计信息过期,MySQL 是基于采样估算的,数据倾斜严重时会选错。
第三步是修复。SQL 能改就改,把函数包装改成范围条件。改不了的加索引,联合索引要按高频查询列的顺序放。如果怀疑是统计信息不准,先跑 ANALYZE TABLE 刷新一下。真要保底可以 FORCE INDEX 硬推,但要配合 EXPLAIN ANALYZE 验证真实代价,别引入更差的全索引扫描。
日常我也会盯着 sys.schema_index_statistics 和 performance_schema.table_io_waits_summary_by_index_usage,发现 count_star=0 或者 rows_selected/rows_examined 长期低于 1% 的索引,就是失效或者无效索引,该改该删。
https://chat.deepseek.com/share/jqfr120k19i2vcl6e3