1.概述
设计 MySQL
的大叔提出了一个 optimizer trace
的功能,这个功能可以让我们方便的查看优化器生成执行计划的整个过程,这个功能的开启与关闭由系统变量 optimizer_trace
决定。
如果想打开这个功能,必须首先把 enabled 的值改为 on ,就像这样: SET optimizer_trace="enabled=on";
然后我们就可以输入我们想要查看优化过程的查询语句,当该查询语句执行完成后,就可以到 information_schema
数据库下的 OPTIMIZER_TRACE
表中查看完整的优化过程。这个 OPTIMIZER_TRACE
表有4个列,分别是:
(1). QUERY
:表示我们的查询语句。
(2). TRACE
:表示优化过程的JSON格式文本。
(3). MISSING_BYTES_BEYOND_MAX_MEM_SIZE
:由于优化过程可能会输出很多,如果超过某个限制时,多余的文本将不会被显示,这个字段展示了被忽略的文本字节数。
(4). INSUFFICIENT_PRIVILEGES
:表示是否没有权限查看优化过程,默认值是0
,只有某些特殊情况下才会是 1
,我们暂时不关心这个字段的值。
现在我们有一个搜索条件比较多的查询语句,它的执行计划如下:EXPLAIN SELECT * FROM t1 WHERE key1 > 'z' AND key2 < 1000000 AND key3 IN ('a', 'b', 'c') AND common_field = 'abc';
可以看到该查询可能使用到的索引有3
个,那么为什么优化器最终选择了 idx_key1
而不选择其他的索引或者直接全表扫描呢?这时候就可以通过 otpimzer trace
功能来查看优化器的具体工作过程:
(1).SET optimizer_trace="enabled=on";
(2). SELECT * FROM t1 WHERE key1 > 'z' AND key2 < 1000000 AND key3 IN ('a', 'b', 'c') AND common_field = 'abc';
(3). SELECT * FROM information_schema.OPTIMIZER_TRACE\G
QUERY: SELECT * FROM t1 WHERE key1 > 'z' AND key2 < 1000000 AND key3 IN ('a', 'b', 'c') AND common_field = 'abc'
TRACE: {
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"IN_uses_bisection": true
},
{
"expanded_query": "/* select#1 */ select `t1`.`id` AS `id`,`t1`.`key1` AS `key1`,`t1`.`key2` AS `key2`,`t1`.`key3` AS `key3`,`t1`.`key_part1` AS `key_part1`,`t1`.`key_part2` AS `key_part2`,`t1`.`key_part3` AS `key_part3`,`t1`.`common_field` AS `common_field` from `t1` where ((`t1`.`key1` > 'z') and (`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))"
}
]
}
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "((`t1`.`key1` > 'z') and (`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "((`t1`.`key1` > 'z') and (`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))"
},
{
"transformation": "constant_propagation",
"resulting_condition": "((`t1`.`key1` > 'z') and (`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "((`t1`.`key1` > 'z') and (`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))"
}
]
}
},
{
"substitute_generated_columns": {
}
},
{
"table_dependencies": [
{
"table": "`t1`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": [
]
}
]
},
{
"ref_optimizer_key_uses": [
]
},
{
"rows_estimation": [
{
"table": "`t1`",
"range_analysis": {
"table_scan": {
"rows": 3,
"cost": 2.65
},
"potential_range_indexes": [
{
"index": "PRIMARY",
"usable": false,
"cause": "not_applicable"
},
{
"index": "idx_key2",
"usable": true,
"key_parts": [
"key2"
]
},
{
"index": "idx_key1",
"usable": true,
"key_parts": [
"key1",
"id"
]
},
{
"index": "idx_key3",
"usable": true,
"key_parts": [
"key3",
"id"
]
},
{
"index": "idx_key_part",
"usable": false,
"cause": "not_applicable"
}
],
"setup_range_conditions": [
],
"group_index_range": {
"chosen": false,
"cause": "not_group_by_or_distinct"
},
"skip_scan_range": {
"potential_skip_scan_indexes": [
{
"index": "idx_key2",
"usable": false,
"cause": "query_references_nonkey_column"
},
{
"index": "idx_key1",
"usable": false,
"cause": "query_references_nonkey_column"
},
{
"index": "idx_key3",
"usable": false,
"cause": "query_references_nonkey_column"
}
]
},
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "idx_key2",
"ranges": [
"NULL < key2 < 1000000"
],
"index_dives_for_eq_ranges": true,
"rowid_ordered": false,
"using_mrr": false,
"index_only": false,
"in_memory": 1,
"rows": 3,
"cost": 1.31,
"chosen": true
},
{
"index": "idx_key1",
"ranges": [
"'z' < key1"
],
"index_dives_for_eq_ranges": true,
"rowid_ordered": false,
"using_mrr": false,
"index_only": false,
"in_memory": 1,
"rows": 1,
"cost": 0.61,
"chosen": true
},
{
"index": "idx_key3",
"ranges": [
"key3 = 'a'",
"key3 = 'b'",
"key3 = 'c'"
],
"index_dives_for_eq_ranges": true,
"rowid_ordered": false,
"using_mrr": false,
"index_only": false,
"in_memory": 1,
"rows": 3,
"cost": 1.81,
"chosen": false,
"cause": "cost"
}
],
"analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
}
},
"chosen_range_access_summary": {
"range_access_plan": {
"type": "range_scan",
"index": "idx_key1",
"rows": 1,
"ranges": [
"'z' < key1"
]
},
"rows_for_plan": 1,
"cost_for_plan": 0.61,
"chosen": true
}
}
}
]
},
{
"considered_execution_plans": [
{
"plan_prefix": [
],
"table": "`t1`",
"best_access_path": {
"considered_access_paths": [
{
"rows_to_scan": 1,
"access_type": "range",
"range_details": {
"used_index": "idx_key1"
},
"resulting_rows": 1,
"cost": 0.71,
"chosen": true
}
]
},
"condition_filtering_pct": 100,
"rows_for_plan": 1,
"cost_for_plan": 0.71,
"chosen": true
}
]
},
{
"attaching_conditions_to_tables": {
"original_condition": "((`t1`.`key1` > 'z') and (`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))",
"attached_conditions_computation": [
],
"attached_conditions_summary": [
{
"table": "`t1`",
"attached": "((`t1`.`key1` > 'z') and (`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))"
}
]
}
},
{
"finalizing_table_conditions": [
{
"table": "`t1`",
"original_table_condition": "((`t1`.`key1` > 'z') and (`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))",
"final_table_condition ": "((`t1`.`key1` > 'z') and (`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))"
}
]
},
{
"refine_plan": [
{
"table": "`t1`",
"pushed_index_condition": "(`t1`.`key1` > 'z')",
"table_condition_attached": "((`t1`.`key2` < 1000000) and (`t1`.`key3` in ('a','b','c')) and (`t1`.`common_field` = 'abc'))"
}
]
}
]
}
},
{
"join_execution": {
"select#": 1,
"steps": [
]
}
}
]
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
INSUFFICIENT_PRIVILEGES: 0
优化过程大致分为了三个阶段:
prepare
阶段
optimize
阶段
execute
阶段
我们所说的基于成本的优化主要集中在 optimize
阶段,对于单表查询来说,我们主要关注 optimize
阶段的 “rows_estimation
” 这个过程,这个过程深入分析了对单表查询的各种执行方案的成本;对于多表连接查询来说,我们更多需要关注 “considered_execution_plans
” 这个过程,这个过程里会写明各种不同的连接方式所对应的成本。反正优化器最终会选择成本最低的那种方案来作为最终的执行计划,也就是我们使用 EXPLAIN
语句所展现出的那种方案。如果有小伙伴对使用 EXPLAIN 语句展示出的对某个查询的执行计划很不理解,大家可以尝试使用 optimizer trace
功能来详细了解每一种执行方案对应的成本,相信这个功能能让大家更深入的了解 MySQL
查询优化器。