题目:MySQL 为什么会选错索引,如何解决?
难度:medium
标签:db/mysql/index, mysql/index, difficulty/medium
评分:⭐⭐⭐☆(3/5)
MySQL 走索引,可能是预期不符,它计算的执行计划里边,它看到的成本可能与我们预估的不一样,或者是存在计算的时候,它统计的数据和实际的数据存在误差。这时候就会导致它计算的成本走它其他的索引,而不选择我们预期中的索引。这时候我们可以其实可以做强走索引,或者是重新生成索引,把数据索引数据刷新一下。不是重新生成索引,是刷新索引。这时候的话可以让 MySQL 去重新计算这个索引的成本,然后可以看一下是否走了我们预期索引。然后就是实在不行就强加索引,走强制索引,我们指定强制我们那个索引,但加强制索引有风险。因为会导致一些死硬的编程,这因为加强制索引会写到 SQL 里边。之前碰见过类似的情况,就是加强制索引后,对应的这个字段的索引后续觉得没有用了被删除了,但是业务里边有加强索引的地方,直接导致对应业务报错生产事故。
优点:
ANALYZE TABLE 刷新统计信息FORCE INDEX 硬编码导致后续索引被删报错),有实战感不足:
cost = rows × cpu_cost + pages × io_costinnodb_stats_persistent_sample_pages(默认 20 页),倾斜数据下必翻车ANALYZE TABLE t UPDATE HISTOGRAM ON col,专门解决长尾分布optimizer_switch、eq_range_index_dive_limit、prefer_ordering_index、index_mergeEXPLAIN FORMAT=JSON 里 cost_info / attached_condition 对比IGNORE INDEX 与 FORCE INDEX 的区别performance_schema.table_io_waits_summary_by_index_usage.count_star=0 找无索引补充:
IN (...) 元素过多越过 eq_range_index_dive_limitEXPLAIN ANALYZE 验证真实代价/+ IGNORE_INDEX(t idx) / 或 /+ FORCE_INDEX(t idx) /核心考点定位:理解 MySQL 优化器基于成本模型的决策机制,掌握选错索引的诊断与修复手段。
知识点拆解:
cost = cpu_cost × rows + io_cost × pages_read。它会为每种可能计划算 cost,选最小的。cost 是基于统计信息估算出来的,不是真实扫描值,这就是选错索引的根源。innodb_stats_persistent=ON),用 innodb_stats_persistent_sample_pages(默认 20 页)随机采样估算基数。采样只有 20 页,数据倾斜、热点集中在少数页时估算必失准。ANALYZE,优化器用旧基数算IN 元素过多:超过 eq_range_index_dive_limit(默认 200),从精确 index dive 切换成基于统计估算(a)、(a,b)、(a,c) 都存在,优化器挑了错误的ORDER BY 排序列:优化器偏好 prefer_ordering_index=on,即使成本更高也倾向避免 filesortindex_merge 陷阱:优化器认为 index merge 更快但实际更慢EXPLAIN 看 key、rows、Extra、key_lenEXPLAIN FORMAT=JSON:看 cost_info.query_cost、attached_condition、missing_covering_indexEXPLAIN ANALYZE(8.0):看真实执行耗时与估算的差距SHOW INDEX FROM t:看 Cardinality 是否合理sys.statement_analysis:找慢查询关联的 index 使用OR 为 UNION)ANALYZE TABLE 或 8.0 建直方图 ANALYZE TABLE t UPDATE HISTOGRAM ON coloptimizer_switch、eq_range_index_dive_limit/+ FORCE_INDEX(t idx) /,写在 SQL 里同样有硬编码风险performance_schema.table_io_waits_summary_by_index_usage,count_star=0 的索引是候选删除EXPLAIN ANALYZE 看真实代价考察方向:
延伸递进面试题:
eq_range_index_dive_limit 是什么?为什么超过它反而选错?IN 或范围条件里 index dive 的最大次数。超过后优化器退回用 stats.records 均分估算,倾斜数据下偏差巨大。prefer_ordering_index 是干什么的?为什么会导致"更贵的索引被选中"?on,优化器偏好能避免 filesort 的索引。即使该索引要扫描更多行,只要省了排序就选它。ANALYZE TABLE t UPDATE HISTOGRAM ON col 会把列的值分布采样存下来,帮助优化器更准地估算基数。适合长尾分布(如 status 字段 90% 都是 1)场景。面试官您好,MySQL 选错索引本质上是基于成本的优化器(CBO)算错了成本。它做决策的核心是 cost = rows × cpu_cost + pages × io_cost,rows 和 pages 都来自统计信息,而统计信息是 InnoDB 通过 innodb_stats_persistent_sample_pages 采样估算出来的,默认只采样 20 页。所以只要数据有倾斜、采样页没覆盖到热点分布,优化器就会算错行数,选到错误的索引。
典型场景有几个:一是统计信息过期,数据大量增删后没跑 ANALYZE,优化器用旧数据算;二是数据倾斜,某个值占比 90% 但采样没扫到;三是 IN 元素超过 eq_range_index_dive_limit(默认 200),优化器从精确 index dive 退回均分估算;四是排序偏好,prefer_ordering_index=on 让优化器倾向选能避免 filesort 的索引,即使要扫更多行。
排查上我会先跑 EXPLAIN 看 key、rows、Extra,再用 EXPLAIN FORMAT=JSON 看 cost_info 和 attached_condition,MySQL 8.0 最好用 EXPLAIN ANALYZE 直接看真实耗时和估算的差异,差距越大说明统计信息越不准。
修复我按优先级来:先改 SQL,把走不到索引的写法改掉(比如函数包装改范围条件,OR 拆 UNION);再考虑补索引或者调整联合索引顺序;然后跑 ANALYZE TABLE 刷新统计,如果是长尾分布的场景,MySQL 8.0 还可以建直方图 ANALYZE TABLE t UPDATE HISTOGRAM ON col;实在不行再动 optimizer_switch 或者硬指定。
FORCE INDEX 我一般放最后手段。它写死在 SQL 里有两个风险:一是数据变化后强走可能变成全索引扫描反而更慢;二是我之前踩过坑——代码里 FORCE INDEX 硬编码,后来团队把这个索引当冗余删了,业务直接生产报错。所以我的原则是 FORCE INDEX 上线前必须 EXPLAIN ANALYZE 验证真实代价,而且要有索引治理流程,通过 performance_schema.table_io_waits_summary_by_index_usage 定期扫 count_star=0 的索引,删之前先 grep 代码库确认没人引用。
https://chat.deepseek.com/share/9fe4n94b1weytntp9b