导读:本期聚焦于小伙伴创作的《如何通过MySQL的optimizer_trace看清基于成本的执行计划选择过程?》,敬请观看详情。一条SQL在MySQL里到底为什么选了全表扫描而不是索引范围扫描,光看explain常常说不清原因。optimizer_trace是内置的执行计划调试工具,它会把优化器从语义转换、代价估算到最终选路的每一步都记成JSON。本文先讲清楚代价模型里io_cost和cpu_cost怎么算,再说明怎么用optimizer_trace把候选访问路径的cost摊开比对。很多人以为开trace会影响线上性能,其实会话级开启只记录当前连接,且能精准定位优化器误判统计信息的坑。掌握这套方法,就能把慢查询的优化从猜变成看。

MySQL的基于成本优化器(cost-based optimizer)在生成执行计划时,会对每一种可能的表访问路径、连接顺序和索引使用方式计算一个数值化的代价,然后挑选总代价最小的方案。但explain只给出结果,不展示中间比较过程。optimizer_trace正是官方提供的执行计划调试接口,它能把优化器内部决策路径完整输出为结构化JSON,让开发者直接看到每张表的候选计划及其cost组成。

如何通过MySQL的optimizer_trace看清基于成本的执行计划选择过程?

一、optimizer_trace的基本开启与查看

optimizer_trace默认是关闭的,因为它会带来一定的内存与CPU开销。我们可以在会话级别单独打开,只影响当前连接,因此非常适合在测试环境或临时排查线上问题时使用。打开后执行目标SQL,再查询information_schema.optimizer_trace表即可拿到完整的trace内容。

下面是一段典型的开启与查看脚本。注意trace内容以JSON字符串存放在TRACE列中,长度可能很大,需要客户端支持长文本显示。

-- 开启当前会话的optimizer_trace,只记录本连接
SET optimizer_trace='enabled=on';

-- 执行需要分析的SQL
SELECT * FROM orders WHERE user_id = 100 AND status = 'paid' ORDER BY created_at DESC LIMIT 10;

-- 查看优化器追踪结果
SELECT TRACE FROM information_schema.optimizer_traceG

-- 关闭trace
SET optimizer_trace='enabled=off';

从输出中可以看到多个一级节点,例如join_preparation描述SQL重写,join_optimization包含代价计算与计划选择,join_execution则是执行阶段信息。我们重点关注join_optimization里的rows_estimated、cost_info以及considered_execution_plans。

二、cost-based优化器的代价模型

MySQL优化器估算代价时主要考虑两类成本:io_cost代表从存储引擎读取数据页的磁盘或缓冲池IO开销,cpu_cost代表在内存中处理记录(比如比较键值、做条件过滤)的CPU开销。两者相加构成某个访问路径的总cost。优化器会为同一张表生成多个候选路径,比如全表扫描、不同索引的范围扫描、索引覆盖扫描等。

在optimizer_trace的trace JSON中,每个候选计划都会带有一个cost_info对象,里面列出read_cost、eval_cost和prefix_cost等字段。read_cost近似对应IO代价,eval_cost对应CPU代价,prefix_cost则是到当前表为止的整体前缀代价,用于多表连接时的比较。理解这些字段,才能判断优化器为什么偏爱某个索引。

{
  "plan_prefix": [],
  "table": "`orders`",
  "best_access_path": {
    "considered_access_paths": [
      {
        "access_type": "ref",
        "index": "idx_user_status",
        "rows": 120,
        "cost_info": {
          "read_cost": 48.2,
          "eval_cost": 24.0,
          "prefix_cost": 72.2,
          "data_read_per_join": "19K"
        }
      },
      {
        "access_type": "scan",
        "rows": 98000,
        "cost_info": {
          "read_cost": 5200.0,
          "eval_cost": 19600.0,
          "prefix_cost": 24800.0,
          "data_read_per_join": "15M"
        }
      }
    ]
  }
}

上面这段简化的trace显示,优化器认为使用idx_user_status索引的ref访问只需72.2的代价,而全表扫描需要24800,因此显然会选择索引。但如果统计信息过期,rows估算偏差变大,就可能反过来选错。这也是为什么有时我们明明有索引,MySQL却走了全表扫描。

三、利用trace定位统计信息误判

当执行计划不符合预期时,第一步应看trace里considered_access_paths中每个路径的rows估算值。这个值来自表的统计信息(如innodb_index_stats)。如果实际数据分布倾斜,而统计采样不准,优化器会低估或高估某路径的行数,进而算错cost。

举个例子,某订单表user_id=100实际只有10行,但统计信息显示平均每个user_id有5000行,优化器就会觉得走索引不如扫描小表划算。通过trace发现rows被估高后,可以手动更新统计信息:

-- 重新采集表统计信息
ANALYZE TABLE orders;

-- 再次开启trace验证计划变化
SET optimizer_trace='enabled=on';
SELECT * FROM orders WHERE user_id = 100;
SELECT TRACE FROM information_schema.optimizer_traceG
SET optimizer_trace='enabled=off';

对比两次trace里的rows与cost_info,就能确认是否因统计信息导致优化器误判。在某些无法改统计信息的场景,也可以用FORCE INDEX提示,但根本解决还是保持统计准确。

四、多表连接中的prefix_cost分析

对于多表JOIN,优化器会枚举不同的连接顺序,每一步都计算prefix_cost,也就是把前面几张表连接完之后再接入当前表的总代价。trace中的considered_execution_plans数组会列出每一种连接顺序的评估结果,我们可以从中看出为什么优化器把小表放到了驱动位置。

以下SQL演示了一个两表关联的分析过程。通过trace能够确认优化器是否选择了它宣称的最优顺序,以及中间临时表的行数估算。

SET optimizer_trace='enabled=on';

SELECT u.name, o.amount
FROM users u
JOIN orders o ON o.user_id = u.id
WHERE u.city = 'Beijing' AND o.status = 'paid';

SELECT TRACE FROM information_schema.optimizer_traceG
SET optimizer_trace='enabled=off';

在输出的considered_execution_plans里,会看到先访问users还是orders的两种方案及其prefix_cost。如果某方案虽然后面cost低但中间结果集暴涨,优化器通常会避开。借助这些信息,我们可以针对性建联合索引或调整WHERE条件写法来引导优化器。

五、使用注意事项与性能影响

虽然optimizer_trace很有用,但它不是免费午餐。开启后优化器需要把决策过程序列化成JSON,会增加内存分配和CPU时间。因此生产环境务必使用会话级开关,执行完一条问题SQL就马上关闭,且不要在高并发账户上长期开启。

另外trace内容可能非常大,尤其是复杂SQL,查询information_schema.optimizer_trace时建议只取TRACE列,并在客户端关闭自动格式化以防卡死。如果只想看某部分,可以用JSON提取函数处理,但多数情况直接阅读原始trace更直观。

-- 仅对单个查询开启,用完即关,避免影响其他请求
SET SESSION optimizer_trace='enabled=on';
-- 你的慢SQL
SELECT COUNT(*) FROM large_table WHERE col_a > 100;
SELECT TRACE FROM information_schema.optimizer_trace;
SET SESSION optimizer_trace='enabled=off';

只要控制好作用范围,optimizer_trace就是排查“为什么MySQL不选我的索引”这类问题最权威的手段,比单纯靠经验猜要可靠得多。

optimizer_traceMySQLcost_based_optimizer修改时间:2026-08-09 01:12:35

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。