开启optimizer trace前必须设optimizer_trace="enabled=on"、调大optimizer_trace_max_mem_size至10mb、用select语句触发;trace存于information_schema.optimizer_trace表,需手动查询,关键看trace字段及missing_bytes_beyond_max_mem_size是否为0。

开启Optimizer Trace前必须确认的三件事
MySQL 5.7 的 optimizer_trace 是个诊断型功能,不是默认打开的,而且开销不小——它会记录优化器每一步的决策过程,但不参与实际执行。如果你只看到空结果或 NULL,大概率是没配对参数。
-
optimizer_trace必须设为enabled=on(不是1或true) -
optimizer_trace_max_mem_size默认只有 1MB,复杂查询容易截断,建议先设成10485760(10MB) - 必须用
SELECT触发优化器工作;EXPLAIN不会写入 trace,INSERT/UPDATE/DELETE也不会(除非带SELECT子句)
执行一次带 trace 的查询并读取结果
不能指望执行完 SQL 就自动吐出 trace——它被存在会话级的 information_schema.OPTIMIZER_TRACE 表里,且只保留最近一次的记录。你得手动查。
- 先开启 trace:
SET optimizer_trace="enabled=on", optimizer_trace_max_mem_size=10485760;
- 再跑目标查询,比如:
SELECT * FROM orders WHERE user_id = 123 AND status = 'paid' ORDER BY created_at DESC LIMIT 10;
- 立刻查 trace:
SELECT * FROM information_schema.OPTIMIZER_TRACE\G
(注意末尾的\G,否则 JSON 字段显示不全)
返回结果里最关键的字段是 TRACE,它是格式化后的 JSON;MISSING_BYTES_BEYOND_MAX_MEM_SIZE 如果大于 0,说明 trace 被截断了,得调大 optimizer_trace_max_mem_size 重试。
看懂 trace 里 cost 相关的关键字段
真正反映“成本”的不是某个单一数字,而是嵌套在 steps 里的多层估算值。重点盯这几个位置:
-
join_execution → steps → {table: "orders", ...} → rows_estimation → records:这是预估扫描行数,比EXPLAIN的rows更细(含条件过滤后) -
join_execution → steps → {table: "orders", ...} → cost_info → eval_cost:表达式计算成本,高说明 WHERE 条件函数多或类型转换频繁 -
join_execution → steps → {table: "orders", ...} → cost_info → prefix_cost:整条访问路径的累计成本,最终排序、临时表等操作也计入其中 - 如果出现
"using_filesort": true或"using_temporary_table": true,对应成本项里会有明显跳升,这就是性能瓶颈的信号
常见误判和陷阱
很多人把 trace 里的 cost 当成真实耗时,其实它只是优化器内部的抽象单位,跟毫秒无关;不同版本间数值也不可比。更危险的是忽略上下文:
- trace 只反映“当前会话”的优化路径,
SQL_NO_CACHE或SQL_CALC_FOUND_ROWS会影响是否走缓存逻辑,进而改变 trace 内容 - 如果表有分区,
range_analysis_per_partition会单独展开每个分区的成本,但主prefix_cost是汇总值,别漏看子节点 -
index_merge场景下,trace 会列出多个索引分别的成本,但最终选哪个,得看chosen_range_access_summary下的final_cost对比 - trace 不包含执行阶段的 I/O 等待、锁等待、buffer pool 命中率——这些得靠
performance_schema或慢日志补全
真正难的是把 cost 数字和你的索引设计、数据分布、统计信息新鲜度串起来。比如 records 预估严重偏离实际,八成是 ANALYZE TABLE 没跑过,或者采样率太低。











