暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

MySQL执行器究竟如何选择索引的

编程阁楼 2020-08-04
335
前文我们聊过MySQL执行器存在选错索引的情况(回看《记一次线上SQL执行很慢的问题排查过程》),那么MySQL执行器究竟是如何选择合适的索引的呢?

我们用以下语句进行演示:
select * from dubbo_invoke2 where application != 'experts-web';
查看其执行计划:

显然,该语句不会使用到索引。

换种写法:
EXPLAIN SELECT application FROM dubbo_invoke2 WHERE application != 'experts-web';

查看其执行计划如下:

熟悉MySQL的小伙伴可能一眼就看出来上述两个sql语句的差别,没错,第二种写法,MySQL只需要在索引树中进行查询,不需要回表查询,这里使用到了索引。


有时候,MySQL底层究竟选择哪种索引可能跟我们想象的并不一样。那么有没有办法知道MySQL执行器究竟是如何选择合适的索引呢?这就需要用到optimizer_trace功能了,因开启trace会影响MySQL性能,所以一般只是临时分析sql使用,用完之后请立即关闭。

开启trace工具:
set optimizer_trace='enabled=on';
set optimizer_trace_max_mem_size=1000000;
set end_markers_in_json=on;
开启后,我们执行查询:
SELECT * FROM dubbo_invoke2 WHERE application != 'experts-web';
select * FROM information_schema.optimizer_trace;
查看trace结果:
{
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `dubbo_invoke2`.`uu_id` AS `uu_id`,`dubbo_invoke2`.`application` AS `application`,`dubbo_invoke2`.`app_type` AS `app_type`,`dubbo_invoke2`.`service` AS `service`,`dubbo_invoke2`.`method` AS `method`,`dubbo_invoke2`.`consumer_host` AS `consumer_host`,`dubbo_invoke2`.`consumer_port` AS `consumer_port`,`dubbo_invoke2`.`provider_host` AS `provider_host`,`dubbo_invoke2`.`provider_port` AS `provider_port`,`dubbo_invoke2`.`success` AS `success`,`dubbo_invoke2`.`failure` AS `failure`,`dubbo_invoke2`.`elapsed` AS `elapsed`,`dubbo_invoke2`.`concurrent` AS `concurrent`,`dubbo_invoke2`.`max_elapsed` AS `max_elapsed`,`dubbo_invoke2`.`max_concurrent` AS `max_concurrent`,`dubbo_invoke2`.`invoke_date` AS `invoke_date`,`dubbo_invoke2`.`invoke_hour` AS `invoke_hour`,`dubbo_invoke2`.`invoke_time` AS `invoke_time`,`dubbo_invoke2`.`gmt_create` AS `gmt_create` from `dubbo_invoke2` where (`dubbo_invoke2`.`application` <> 'experts-web')"
}
] /* steps */
} /* join_preparation */
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "(`dubbo_invoke2`.`application` <> 'experts-web')",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "(`dubbo_invoke2`.`application` <> 'experts-web')"
},
{
"transformation": "constant_propagation",
"resulting_condition": "(`dubbo_invoke2`.`application` <> 'experts-web')"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "(`dubbo_invoke2`.`application` <> 'experts-web')"
}
] /* steps */
} /* condition_processing */
},
{
"substitute_generated_columns": {
} /* substitute_generated_columns */
},
{
"table_dependencies": [
{
"table": "`dubbo_invoke2`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": [
] /* depends_on_map_bits */
}
] /* table_dependencies */
},
{
"ref_optimizer_key_uses": [
] /* ref_optimizer_key_uses */
},
{
"rows_estimation": [ --预估访问成本
{
"table": "`dubbo_invoke2`",
"range_analysis": {
"table_scan": { --全表扫描情况
"rows": 71159, --扫描行数
"cost": 15359 --查询成本
} /* table_scan */,
"potential_range_indexes": [ --查询可能使用的索引
{
"index": "PRIMARY",
"usable": false,
"cause": "not_applicable"
},
{
"index": "idx_method",
"usable": false,
"cause": "not_applicable"
},
{
"index": "idx_application",
"usable": true,
"key_parts": [
"application",
"uu_id"
] /* key_parts */
},
{
"index": "idx_service",
"usable": false,
"cause": "not_applicable"
},
{
"index": "idx_type",
"usable": false,
"cause": "not_applicable"
}
] /* potential_range_indexes */,
"setup_range_conditions": [
] /* setup_range_conditions */,
"group_index_range": {
"chosen": false,
"cause": "not_group_by_or_distinct"
} /* group_index_range */,
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "idx_application",
"ranges": [
"application < experts-web",
"experts-web < application"
] /* ranges */,
"index_dives_for_eq_ranges": true,
"rowid_ordered": false,
"using_mrr": false,
"index_only": false,
"rows": 35922,
"cost": 43108,
"chosen": false,
"cause": "cost"
}
] /* range_scan_alternatives */,
"analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
} /* analyzing_roworder_intersect */
} /* analyzing_range_alternatives */
} /* range_analysis */
}
] /* rows_estimation */
},
{
"considered_execution_plans": [
{
"plan_prefix": [
] /* plan_prefix */,
"table": "`dubbo_invoke2`",
"best_access_path": { --最优访问路径
"considered_access_paths": [
{
"rows_to_scan": 71159,
"access_type": "scan", --全表扫描
"resulting_rows": 71159,
"cost": 15357,
"chosen": true --是否选择
}
] /* considered_access_paths */
} /* best_access_path */,
"condition_filtering_pct": 100,
"rows_for_plan": 71159,
"cost_for_plan": 15357,
"chosen": true
}
] /* considered_execution_plans */
},
{
"attaching_conditions_to_tables": {
"original_condition": "(`dubbo_invoke2`.`application` <> 'experts-web')",
"attached_conditions_computation": [
] /* attached_conditions_computation */,
"attached_conditions_summary": [
{
"table": "`dubbo_invoke2`",
"attached": "(`dubbo_invoke2`.`application` <> 'experts-web')"
}
] /* attached_conditions_summary */
} /* attaching_conditions_to_tables */
},
{
"refine_plan": [
{
"table": "`dubbo_invoke2`"
}
] /* refine_plan */
}
] /* steps */
} /* join_optimization */
},
{
"join_execution": {
"select#": 1,
"steps": [
] /* steps */
} /* join_execution */
}
] /* steps */
}
以上内容较多,重点是上述增加注释的地方,可以看到上述查询语句MySQL执行器选择的是全表扫描,因为它认为全表扫描成本更低。

我们换个语句再来看下:
SELECT * FROM dubbo_invoke2 WHERE application = 'experts-web';
select * FROM information_schema.optimizer_trace;
查看trace结果
{
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `dubbo_invoke2`.`uu_id` AS `uu_id`,`dubbo_invoke2`.`application` AS `application`,`dubbo_invoke2`.`app_type` AS `app_type`,`dubbo_invoke2`.`service` AS `service`,`dubbo_invoke2`.`method` AS `method`,`dubbo_invoke2`.`consumer_host` AS `consumer_host`,`dubbo_invoke2`.`consumer_port` AS `consumer_port`,`dubbo_invoke2`.`provider_host` AS `provider_host`,`dubbo_invoke2`.`provider_port` AS `provider_port`,`dubbo_invoke2`.`success` AS `success`,`dubbo_invoke2`.`failure` AS `failure`,`dubbo_invoke2`.`elapsed` AS `elapsed`,`dubbo_invoke2`.`concurrent` AS `concurrent`,`dubbo_invoke2`.`max_elapsed` AS `max_elapsed`,`dubbo_invoke2`.`max_concurrent` AS `max_concurrent`,`dubbo_invoke2`.`invoke_date` AS `invoke_date`,`dubbo_invoke2`.`invoke_hour` AS `invoke_hour`,`dubbo_invoke2`.`invoke_time` AS `invoke_time`,`dubbo_invoke2`.`gmt_create` AS `gmt_create` from `dubbo_invoke2` where (`dubbo_invoke2`.`application` = 'experts-web')"
}
] /* steps */
} /* join_preparation */
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "(`dubbo_invoke2`.`application` = 'experts-web')",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "(`dubbo_invoke2`.`application` = 'experts-web')"
},
{
"transformation": "constant_propagation",
"resulting_condition": "(`dubbo_invoke2`.`application` = 'experts-web')"
},
{
"transformation": "trivial_condition_removal",
"resulting_condition": "(`dubbo_invoke2`.`application` = 'experts-web')"
}
] /* steps */
} /* condition_processing */
},
{
"substitute_generated_columns": {
} /* substitute_generated_columns */
},
{
"table_dependencies": [
{
"table": "`dubbo_invoke2`",
"row_may_be_null": false,
"map_bit": 0,
"depends_on_map_bits": [
] /* depends_on_map_bits */
}
] /* table_dependencies */
},
{
"ref_optimizer_key_uses": [
{
"table": "`dubbo_invoke2`",
"field": "application",
"equals": "'experts-web'",
"null_rejecting": false
}
] /* ref_optimizer_key_uses */
},
{
"rows_estimation": [
{
"table": "`dubbo_invoke2`",
"range_analysis": {
"table_scan": {
"rows": 71159,
"cost": 15359
} /* table_scan */,
"potential_range_indexes": [
{
"index": "PRIMARY",
"usable": false,
"cause": "not_applicable"
},
{
"index": "idx_method",
"usable": false,
"cause": "not_applicable"
},
{
"index": "idx_application",
"usable": true,
"key_parts": [
"application",
"uu_id"
] /* key_parts */
},
{
"index": "idx_service",
"usable": false,
"cause": "not_applicable"
},
{
"index": "idx_type",
"usable": false,
"cause": "not_applicable"
}
] /* potential_range_indexes */,
"setup_range_conditions": [
] /* setup_range_conditions */,
"group_index_range": {
"chosen": false,
"cause": "not_group_by_or_distinct"
} /* group_index_range */,
"analyzing_range_alternatives": {
"range_scan_alternatives": [
{
"index": "idx_application",
"ranges": [
"experts-web <= application <= experts-web"
] /* ranges */,
"index_dives_for_eq_ranges": true,
"rowid_ordered": true,
"using_mrr": false,
"index_only": false,
"rows": 35579,
"cost": 42696,
"chosen": false,
"cause": "cost"
}
] /* range_scan_alternatives */,
"analyzing_roworder_intersect": {
"usable": false,
"cause": "too_few_roworder_scans"
} /* analyzing_roworder_intersect */
} /* analyzing_range_alternatives */
} /* range_analysis */
}
] /* rows_estimation */
},
{
"considered_execution_plans": [
{
"plan_prefix": [
] /* plan_prefix */,
"table": "`dubbo_invoke2`",
"best_access_path": { --最优访问路径
"considered_access_paths": [
{
"access_type": "ref",
"index": "idx_application", -- 索引
"rows": 35579,
"cost": 10491,
"chosen": true -- 是否选中
},
{
"rows_to_scan": 71159,
"access_type": "scan",
"resulting_rows": 71159,
"cost": 15357,
"chosen": false
}
] /* considered_access_paths */
} /* best_access_path */,
"condition_filtering_pct": 100,
"rows_for_plan": 35579,
"cost_for_plan": 10491,
"chosen": true
}
] /* considered_execution_plans */
},
{
"attaching_conditions_to_tables": {
"original_condition": "(`dubbo_invoke2`.`application` = 'experts-web')",
"attached_conditions_computation": [
] /* attached_conditions_computation */,
"attached_conditions_summary": [
{
"table": "`dubbo_invoke2`",
"attached": null
}
] /* attached_conditions_summary */
} /* attaching_conditions_to_tables */
},
{
"refine_plan": [
{
"table": "`dubbo_invoke2`"
}
] /* refine_plan */
}
] /* steps */
} /* join_optimization */
},
{
"join_execution": {
"select#": 1,
"steps": [
] /* steps */
} /* join_execution */
}
] /* steps */
}
重点还是best_access_path部分,可以看到上述语句使用到了idx_application这个单列索引。



换个稍微复杂点的语句:
EXPLAIN SELECT * FROM dubbo_invoke2 WHERE application = 'experts-web' AND service = 'a' AND method = 'b';

同样,看trace结果可以看到执行器选择的过程,这里就不再贴trace记录了,感兴趣的小伙伴可以自己进行演练。



总之,MySQL是基于执行成本分析后,动态选择的索引,不同的查询条件最终索引效果可能会完全不同,但我们依然可以根据执行器的trace结果发现其选择过程的规律。
文章转载自编程阁楼,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论