OPTIMIZER_TRACE 是 MySQL 5.6+ 的执行计划调试工具,完整暴露优化器决策过程,需满足版本≥5.6.3、启用 one_line=off 并查 information_schema.OPTIMIZER_TRACE 获取结果。

OPTIMIZER_TRACE 是 MySQL 5.6+ 提供的「执行计划显微镜」,它不只告诉你优化器选了哪个索引,而是完整暴露决策过程:采样了哪些统计值、每个候选索引的成本估算明细、为什么否决了其他路径、甚至包括临时表和排序的代价预估。它不是替代 EXPLAIN 的工具,而是当你看到 EXPLAIN 结果明显不合理时,必须打开的调试开关。
启用 OPTIMIZER_TRACE 前必须做的三件事
直接开 trace 不等于能看懂 trace —— 多数人卡在这一步就放弃了:
- 确保 MySQL 版本 ≥ 5.6.3(低版本 trace 输出不全或缺失关键字段)
- 执行
SET optimizer_trace="enabled=on,one_line=off",one_line=off是刚需,否则 JSON 被压成一行根本没法读 - 在同一个会话中执行目标 SQL 后,立刻查
SELECT * FROM information_schema.OPTIMIZER_TRACE;trace 内容**只保留最近一次查询**,且会话断开即清空
从 trace JSON 里快速定位“选错索引”的证据链
别通读整个 JSON —— 直接聚焦三个关键节点:
-
"steps"数组里的"considered_access_paths"段:这里列出所有被评估的索引路径,每条含"cost"、"rows"、"chosen"字段。重点比对:被选中的索引"chosen": true的 cost 是否真的显著低于其它?如果indx_usercost=2007326 而indx_ctimecost=670092 却没被选,说明 cost 计算本身有偏差 -
"range_analysis"子节:检查"index_dives_for_eq_ranges"是否为true。若为false,表示优化器跳过了精确的索引 dive(即没真正去 B+ 树里数行数),仅靠过期的统计信息估算 —— 这是统计不准导致误判的铁证 -
"analyzing_range_alternatives"中的"cause"字段:常见值如"cost"(纯成本驱动)、"covering_index"(因覆盖而选)、"filesort"(为避免排序而选)。如果看到"cause": "filesort"但实际查询没ORDER BY,大概率是优化器误判了排序需求
trace 显示 cost 接近时,真正瓶颈往往藏在“隐性开销”里
当两个索引的预估 cost 差距小于 5%,优化器的决策就极易受微小误差影响。此时要盯住 trace 里更隐蔽的字段:
-
"io_cost"和"cpu_cost"分项:全表扫描可能io_cost高但cpu_cost低;索引扫描反之。如果磁盘 I/O 实际已饱和,而 trace 仍低估io_cost,就会倾向错误索引 -
"used_table_statistics"里的"table_rows"和各索引的"cardinality":对比SHOW INDEX FROM table_name输出,若cardinality偏差 > 20%,说明采样严重失真 —— 这是ANALYZE TABLE最该介入的信号 -
"best_covering_index"字段是否存在:若存在但未被选,意味着优化器宁可多回表也不愿用覆盖索引,通常指向optimizer_switch中use_index_extensions=off或内存配置限制
trace 看懂了,但问题没解决?下一步动作必须具体
trace 只诊断,不治病。根据 trace 暴露的根因,对应动作要精准:
- 发现
"index_dives_for_eq_ranges": false→ 执行ANALYZE TABLE table_name,而非依赖自动采样 - 发现某索引
cardinality严重偏离实际(如性别字段显示 cardinality=1000 但实际只有 2 个值)→ 删除该索引,低区分度字段索引本身就是性能毒药 - 发现
"cause": "filesort"但业务上完全不需要排序 → 检查 SQL 是否隐含了GROUP BY或窗口函数,或确认ORDER BY子句是否被 ORM 自动生成 - trace 显示正确索引 cost 更低却未被选 → 用
FORCE INDEX临时兜底,同时排查该索引是否被optimizer_switch中的skip_scan=on等开关抑制
真正难的不是看懂 trace,而是把 JSON 里一行行数字映射到物理表结构、数据分布和线上负载的真实约束上。每次 trace 分析后,务必用 EXPLAIN FORMAT=TREE(8.0+)交叉验证执行计划是否同步更新 —— 否则你优化的只是幻觉。


















