MySQL 5.7单列索引过多导致优化器陷入“选择困难症”,因需对每个索引串行执行index dive估算成本,dive次数多、IO开销大、统计信息孤立误判,易选错执行计划致全表扫描。

MySQL 5.7 中单列索引过多,会让优化器在生成执行计划时陷入“选择困难症”——它不是卡在 IO 或内存上,而是卡在穷举和估算上。
优化器要为每个单列索引单独做 index dive
当 WHERE 条件涉及多个可选字段(比如 WHERE status = ? AND user_id = ? AND created_at > ?),而表上又存在 idx_status、idx_user_id、idx_created_at 等一堆单列索引时,优化器必须对每个索引单独执行一次 index dive(下潜到 B+ 树叶子页统计匹配行数)来预估成本。
这个过程是串行的、不可跳过的,且每次 dive 都要访问索引页(可能触发磁盘 IO)。索引越多,dive 次数越多,单次查询的“分析阶段”就拖得越长——你看到的“SQL 执行慢”,可能前 80% 时间花在还没真正开始读数据之前。
- 默认
eq_range_index_dive_limit=200只控制 IN 列表大小,不控制索引数量;哪怕 IN 只有 1 个值,27 个单列索引照样要 dive 27 次 - index dive 的开销与索引深度正相关;B+ 树越深(数据量越大),每次 dive 越耗时
- 并发高时,大量连接同时做 index dive,容易争抢 buffer pool latch 和索引页缓存,进一步放大延迟
统计信息质量差 + 索引冗余加剧误判
MySQL 5.7 没有直方图,只依赖采样统计(ANALYZE TABLE 生成的 cardinality)。单列索引越多,各列统计信息越孤立,优化器无法感知字段间相关性(比如 status='PAID' 时 user_id 分布极不均匀),只能机械叠加估算误差。
安装、修复、更新或打包一个基于 Python 和 PyMySQL 实现的 Windows `mysql` 命令。适用于用户希望在 PowerShell/CMD 中输入 `mysql` 时重定向到 Python 实现、需要交互式输入 MySQL 连接信息、需要同步到当前用户本地 bin 目录,或需要将该能力封装成可公开复用的 Codex skill。
结果就是:本该选 idx_status + idx_created_at 联合走 range,却因两个单列索引的估算值都偏高,误判为“全表扫描更便宜”,直接退回 type: ALL。这不是 bug,是估算失准下的理性错误。
- 冗余索引(如已有
idx_a_b,又建了idx_a)会让优化器多一个无效选项,白费一次 dive -
SHOW INDEX FROM tbl查出 27 行,不代表有 27 种有效筛选能力;很多索引选择性 gender、is_deleted),但优化器仍会认真 dive 它们 - 统计信息过期(长时间没
ANALYZE TABLE)会让问题更隐蔽:看起来索引都在,实际估算值全是错的
为什么联合索引能绕过这个问题?
联合索引天然压缩了优化器的搜索空间。比如把 idx_status、idx_created_at、idx_user_id 合并成 idx_status_created_user (status, created_at, user_id),优化器只需做 1 次 index dive,就能拿到三字段组合的精确基数估算(只要满足最左前缀)。
而且联合索引支持覆盖查询(SELECT status, created_at),避免回表;排序字段放末尾还能消除 Using filesort。一箭多雕,还省掉了 2 次无谓的 dive。
- 联合索引列顺序必须匹配高频查询模式:WHERE 先过滤的字段放前面,ORDER BY 字段放最后
- 不要为了“看起来全面”建
(a,b,c)、(a,c,b)、(b,a,c)这类排列组合;优先保障核心查询路径 - 用
sys.schema_unused_indexes(需启用 performance_schema)定期清理长期未命中的单列索引,比硬调optimizer_search_depth更治本
真正卡住优化器的从来不是索引本身,而是它被迫在一堆低效、孤立、高重复的选项里反复试错。删掉 20 个单列索引,留下 3 个精准的联合索引,执行计划生成时间可能从 200ms 降到 5ms——这中间没有魔法,只有减少无效计算。

















