前面几篇讲了Mysql基于成本的优化,也看了执行计划,今天我们来看看Mysql提供的更详细的执行计划生成过程。
optimizer trace
optimizer trace由来:
1、我们平时查看SQL的执行计划,但是执行计划是相对粗略的,只告诉你使用了什么索引,采用了什么方式。但是没有告诉你它为什么选择这种查询方式。
2、所以,在MySQL 5.6之后,提供了个optimizer trace功能,可以查看Mysql详细的查询计划生成过程。
optimizer trace的使用方式:
- 打开optimizer trace功能 (默认情况下它是关闭的):
SET optimizer_trace=“enabled=on”;- 这里输入你自己的查询语句
SELECT …;- 从OPTIMIZER_TRACE表中查看上一个查询的优化过程
SELECT * FROM information_schema.OPTIMIZER_TRACE;- 可能你还要观察其他语句执行的优化过程,重复上边的第2、3步
…- 当你停止查看语句的优化过程时,把optimizer trace功能关闭
SET optimizer_trace=“enabled=off”;
optimizer trace实践:
我们可以把optimizer_trace开关打开:
SET optimizer_trace="enabled=on";
查看修改情况:
show variables like '%optimizer_trace%';
查询一条sql之后,如:select * from test_index where key1 > 'key120' and key1 < 'key129';
,发现SELECT * FROM information_schema.OPTIMIZER_TRACE;
为
是IDEA的一条SQL,SET SQL_SELECT_LIMIT=501
,因为optimizer_trace默认只显示最近一条SQL的执行计划。
正因为我们使用的是IDEA的数据库插件,它会在我们查询前后增添一些SQL,影响了我们查看optimizer_trace,我们观察到optimizer_trace_limit
、optimizer_trace_offset
两个变量,默认值分别为1和-1,表示只保留一条执行计划,同时指向最近的一条记录,我将optimizer_trace_limit
和optimizer_trace_offset
都修改为5,表示保留最近5条记录,显示前5条。
再次查询成功:
select * from test_index where key1 > 'key120' and key1 < 'key129';
SELECT * FROM information_schema.OPTIMIZER_TRACE;
我们来看一下执行计划生成过程:
{
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
// 将select * 转换为 select 每个列
"expanded_query": "/* select#1 */ select `test_index`.`id` AS `id`,`test_index`.`key1` AS `key1`,`test_index`.`key2` AS `key2`,`test_index`.`key3` AS `key3`,`test_index`.`key_part1` AS `key_part1`,`test_index`.`key_part2` AS `key_part2`,`test_index`.`key_part3` AS `key_part3`,`test_index`.`common_field` AS `common_field` from `test_index` where ((`test_index`.`key1` > 'key120') and (`test_index`.`key1` < 'key129'))"
}
]
}
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
// 对查询条件进行处理
"condition_processing": {
// 原始的查询条件
"condition": "WHERE",
"original_condition": "((`test_index`.`key1` > 'key120') and (`test_index`.`key1` < 'key129'))",
"steps": [
{
// 等值转换
"transformation": "equality_propagation",
"resulting_condition": "((`test_index`.`key1` > 'key120') and (`test_index`.`key1` < 'key129'))"
},
{
// 常量传递转换
"transformation": "constant_propagation",
"resulting_condition": "((`test_index`.`key1` > 'key120') and (`test_index`.`key1` < 'key129'))"
},
{
// 琐碎的条件 (去除没用的条件)
"transformation": "trivial_condition_removal",
"resulting_condition": "((`test_index`.`key1` > 'key120') and (`test_index`.`key1` < 'key129'))"
}
]
}
},
{
-- 替换虚拟生成列
"substitute_generated_columns": {
}
},
{
"table_dependencies": [
{
"table": "`test_index`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": [
]
}
]
},
{
"ref_optimizer_key_uses": [
]
},
{
// 预估不同单表访问方法的访问成本
"rows_estimation": [
{
"table": "`test_index`",
"range_analysis": {
// 全表扫描的行数以及成本
"table_scan": {
"rows": 99316,
"cost": 119181
},
"potential_range_indexes": [
{
// 不能使用主键索引
"index": "PRIMARY",
"usable": false,
"cause": "not_applicable"
},
{
// 不能使用idx_key2索引
"index": "idx_key2",
"usable": false,
"cause": "not_applicable"
},
{
// 不能使用idx_key3索引
"index": "idx_key3",
"usable": false,
"cause": "not_applicable"
},
{
// 不能使用idx_key_part索引
"index": "idx_key_part",
"usable": false,
"cause": "not_applicable"
},
{
// 可以使用idx_key1索引
"index": "idx_key1",
"usable": true,
// 二级索引由 二级索引列+主键索引构成
"key_parts": [
"key1",
"id"
]
}
],
"setup_range_conditions": [
],
// 没有group_by或distinct,不选择通过归集索引的方式
"group_index_range": {
"chosen": false,
"cause": "not_group_by_or_distinct"
},
// 分析各种可能使用的索引的成本
"analyzing_range_alternatives": {
// 通过索引range扫描的方案
"range_scan_alternatives": [
{
"index": "idx_key1",
"ranges": [
"key120 < key1 < key129"
],
"index_dives_for_eq_ranges": true, # 是否使用index dive
"rowid_ordered": false, # 使用该索引获取的记录是否按照主键排序
"using_mrr": false, # 是否使用mrr
"index_only": false, # 是否是索引覆盖访问
"rows": 18254, # 使用该索引获取的记录条数
"cost": 21906, # 使用该索引的成本
"chosen": true # 是否选择该索引
}
],
# 分析使用索引合并的成本
"analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
}
},
# 对于上述单表查询最优的访问方法
"chosen_range_access_summary": {
"range_access_plan": {
"type": "range_scan",
"index": "idx_key1",
"rows": 18254,
"ranges": [
"key120 < key1 < key129"
]
},
"rows_for_plan": 18254,
"cost_for_plan": 21906,
"chosen": true
}
}
}
]
},
{
# 分析各种可能的执行计划
#(对多表查询这可能有很多种不同的方案,单表查询的方案上边已经分析过了,直接选取就好)
"considered_execution_plans": [
{
"plan_prefix": [
],
"table": "`test_index`",
"best_access_path": {
"considered_access_paths": [
{
"rows_to_scan": 18254,
"access_type": "range",
"range_details": {
"used_index": "idx_key1"
},
"resulting_rows": 18254,
"cost": 25557,
"chosen": true
}
]
},
"condition_filtering_pct": 100,
"rows_for_plan": 18254,
"cost_for_plan": 25557,
"chosen": true
}
]
},
{
# 尝试给查询添加一些其他的查询条件
"attaching_conditions_to_tables": {
"original_condition": "((`test_index`.`key1` > 'key120') and (`test_index`.`key1` < 'key129'))",
"attached_conditions_computation": [
{
"table": "`test_index`",
# 重新确认索引的使用,原因是low_limit,查询很少的数据
"rechecking_index_usage": {
"recheck_reason": "low_limit",
"limit": 501,
"row_estimate": 18254
}
}
],
"attached_conditions_summary": [
{
"table": "`test_index`",
"attached": "((`test_index`.`key1` > 'key120') and (`test_index`.`key1` < 'key129'))"
}
]
}
},
{
# 再稍稍的改进一下执行计划
"refine_plan": [
{
"table": "`test_index`",
"pushed_index_condition": "((`test_index`.`key1` > 'key120') and (`test_index`.`key1` < 'key129'))",
"table_condition_attached": null
}
]
}
]
}
},
{
"join_execution": {
"select#": 1,
"steps": [
]
}
}
]
}
小结
查看optimizer trace可以使我们查看更详细的执行计划生成过程。