1. 什么是optimizer_trace?
explain命令可以展示sql语句的最终执行计划,包括是否使用索引、表连接顺序等信息,但它有一个明显的局限性:只展示结果,不解释原因。当我们遇到执行计划不是最优的情况时,仅凭explain的结果很难分析优化器为何会做出这样的选择。
optimizer_trace是mysql提供的一项执行计划跟踪功能,它可以跟踪优化器做出的各种决策(包括表访问方式、开销计算、各种转换等),并将跟踪结果以json格式记录在information_schema.optimizer_trace表中。这使得我们能够深入了解优化器的工作机制,理解为什么选择某个查询计划,查看替代计划及其估计成本。
2. optimizer_trace的基本使用
2.1 启用与配置
optimizer_trace默认是关闭的,因为它会产生一些额外开销。不过,它是轻量级工具,开启关闭简便,且支持会话级别设置,对系统影响很小。
基本启用方法:
-- 在会话中开启optimizer_trace:cite[1]:cite[2] set session optimizer_trace = "enabled=on"; -- 如果需要,还可以设置json格式和内存大小:cite[4]:cite[8] set optimizer_trace="enabled=on",end_markers_in_json=on; set optimizer_trace_max_mem_size=1000000;
参数说明:
optimizer_trace:控制是否开启跟踪功能end_markers_in_json:在json输出中添加结束标记,便于阅读optimizer_trace_max_mem_size:设置跟踪结果的最大内存使用量,防止输出过大被截断
2.2 收集跟踪信息
启用optimizer_trace后,执行需要分析的sql语句,然后查询优化器跟踪信息:
-- 执行需要分析的sql select * from users where age > 25 and salary < 50000; -- 查看跟踪结果:cite[1]:cite[2] select * from information_schema.optimizer_trace\g
2.3 关闭跟踪
完成分析后,建议关闭optimizer_trace以避免不必要的性能开销:
set optimizer_trace = "enabled=off";
3. optimizer_trace输出结构详解
optimizer_trace的输出是一个庞大的json结构,主要包含三个关键阶段:
3.1 join_preparation(准备阶段)
这一阶段主要进行语法解析与检测,包括:
将外连接转换成内连接
合并视图或派生表
处理子查询转换
消除常量和冗余表达式
示例输出:
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `users`.`id` as `id`,`users`.`name` as `name` from `users` where ((`users`.`age` > 25) and (`users`.`salary` < 50000))"
}
]
}此阶段会将sql语句中的*扩展为具体列,并添加对应的表信息。
3.2 join_optimization(优化阶段)
这是优化过程的核心阶段,包含了查询优化的主要逻辑。此阶段通过以下步骤生成高效的查询执行计划(qep):
逻辑等价的查询重写(query rewrite)
基于成本的连接优化(cost-based join optimization)
规则驱动的访问路径选择(rule-based access path selection)
此阶段包含的关键子阶段:
condition_processing:条件处理,优化where和join条件
table_dependencies:分析表依赖关系
ref_optimizer_key_uses:考虑ref类型索引使用
rows_estimation:行数估算和成本分析
3.3 join_execution(执行阶段)
这是sql语句的实际执行阶段,记录执行过程中的相关信息。
4. 关键分析部分:rows_estimation
在优化阶段,rows_estimation是最值得关注的部分之一,它深入分析了单表查询的各种执行方案的成本。
4.1 表扫描分析
"range_analysis": {www.ausxx.com
"table_scan": {
"rows": 10000,
"cost": 2045.25
},
"potential_range_indexes": [m.ausxx.com
{
"index": "primary",
"usable": false,
"cause": "not_applicable"
},
{
"index": "idx_age",
"usable": true,
"key_parts": ["age", "id"]
}
],
"best_covering_index_scan": {wap.ausxx.com
"index": "idx_age",
"cost": 1256.45,
"chosen": falsetsl.ausxx.com
}
}4.2 索引选择分析
优化器会对比不同索引的成本,选择最优方案:
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "idx_age",
"ranges": ["25 < age"],
"index_dives_for_eq_ranges": true,
"rowid_ordered": false,
"using_mrr": false,
"index_only": false,
"rows": 3500,
"cost": 4201.5,
"chosen": false,gov.ausxx.com
"cause": "cost"govzb.ausxx.com
}
]
}5. 实际应用案例
5.1 为什么查询未使用索引?
一个常见的疑问是:为什么有索引但查询没有使用? 通过optimizer_trace,我们可以看到优化器基于成本评估做出的决策。
示例分析:假设有一个表,其中val列有索引,但查询时未使用该索引。通过optimizer_trace的range_analysis部分,可以看到mysql对比了全表扫描和使用val索引两个方案的成本。
在这种情况下,即使使用索引可以减少扫描行数,优化器可能仍然选择全表扫描,原因通常是回表代价过高。当查询需要返回的列不在索引中时,使用索引查找需要额外的回表操作,如果回表数据量较大(通常超过表中约1/5的记录),成本可能会超过全表扫描。
5.2 多表连接顺序选择
对于多表连接查询,optimizer_trace的considered_execution_plans部分会展示各种连接顺序和算法的成本比较,帮助理解优化器为何选择特定的连接顺序。
6. 进阶使用技巧
6.1 处理大型跟踪结果
当跟踪结果很大时,可以将其导出到文件进行分析:
-- 将跟踪结果导出到文件:cite[4] select trace into dumpfile "/tmp/test.trace" from information_schema.optimizer_trace;
6.2 权限考虑
optimizer_trace表有一个insufficient_privileges字段,表示是否有权限查看完整的优化过程,通常为0,特殊情况下为1。
6.3 测试环境中的快捷使用
在测试环境中,可以使用特定的快捷方式启用optimizer_trace,这相当于手动存储当前值、开启跟踪、运行查询、查看结果和恢复原值的过程。
7. 注意事项与最佳实践
性能影响:虽然optimizer_trace是轻量级工具,但在生产环境中仍应谨慎使用,分析完成后及时关闭
结果完整性:设置足够的
optimizer_trace_max_mem_size,避免结果因大小限制被截断统计信息准确性:优化器的决策依赖于统计信息的准确性,定期更新统计信息可以获得更可靠的跟踪分析
结合其他工具:optimizer_trace应与
explain、性能模式(performance schema)等工具结合使用,形成完整的性能分析体系
8. 总结
optimizer_trace是mysql性能分析的强大工具,它揭开了查询优化器的神秘面纱,让我们能够:
✅ 深入理解优化器的工作机制和决策过程
✅ 诊断执行计划选择不合理的原因
✅ 验证索引设计和查询重写的效果
✅ 学习优化器如何权衡不同执行计划的成本
通过掌握optimizer_trace的使用方法和分析技巧,数据库开发和管理人员可以更加精准地定位和解决sql性能问题,提升数据库整体性能。
无论是调优复杂查询,还是理解mysql优化器的行为,optimizer_trace都是一个不可或缺的工具。下次当你对mysql的执行计划有疑问时,不妨打开optimizer_trace,深入探索优化器的思考过程。
到此这篇关于mysql性能分析利器之optimizer_trace使用的文章就介绍到这了,更多相关mysql性能分析optimizer_trace内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论