MySQL 8 C++ 源码解析:EXPLAIN 实现机制
在 MySQL 中,EXPLAIN
是分析查询执行计划的核心工具。其源码实现涉及 查询解析、优化器决策 和 执行计划生成 等多个模块。本文基于 MySQL 8.1.0 源码,深入解析 EXPLAIN
的实现逻辑。
1. 整体架构与入口
(1) 命令分发:mysql_execute_command
所有 SQL 命令的入口函数为 mysql_execute_command
(位于 sql/sql_parse.cc
)。EXPLAIN
作为 DML 的扩展命令,通过 lex->sql_command
标识类型:
switch (lex->sql_command) {
case SQLCOM_EXPLAIN: // 标准 EXPLAIN SELECT/UPDATE/DELETE
case SQLCOM_EXPLAIN_OTHER:// EXPLAIN FOR CONNECTION 或特殊格式
// 调用 DML 执行逻辑
res = lex->m_sql_cmd->execute(thd);
break;
// 其他命令分支...
}
- 关键逻辑:
EXPLAIN
被归类为SQLCOM_EXPLAIN
或SQLCOM_EXPLAIN_OTHER
,触发 DML 执行路径。
2. DML 执行入口:Sql_cmd_dml::execute_inner
(1) 优化与执行计划生成
EXPLAIN
的核心逻辑在 Sql_cmd_dml::execute_inner
(位于 sql/sql_select.cc
):
bool Sql_cmd_dml::execute_inner(THD *thd) {
Query_expression *unit = lex->unit;
// 1. 优化查询表达式(生成 AccessPath)
if (unit->optimize(thd, /*materialize_destination=*/nullptr,
/*create_iterators=*/true, /*finalize_access_paths=*/true))
return true;
// 2. 处理 EXPLAIN 逻辑
if (lex->is_explain()) {
if (explain_query(thd, thd, unit)) return true;
}
return false;
}
- 核心步骤:
unit->optimize
:调用优化器生成执行计划(AccessPath
结构)。explain_query
:根据优化结果生成可读的执行计划输出。
3. 执行计划生成:explain_query
函数
(1) 输出格式处理
explain_query
(位于 sql/opt_explain.cc
)负责将优化器生成的 AccessPath
转换为文本、JSON 或树形格式:
bool explain_query(THD *explain_thd, const THD *query_thd, Query_expression *unit) {
// 1. 判断是否使用迭代器模式(如 JSON/TREE 格式)
if (lex->explain_format->is_iterator_based()) {
return ExplainIterator(explain_thd, query_thd, unit);
}
// 2. 传统格式(TRADITIONAL)
return mysql_explain_query_expression(explain_thd, query_thd, unit);
}
- 分支逻辑:
- 迭代器模式:用于
FORMAT=JSON
或FORMAT=TREE
,调用ExplainIterator
。 - 传统模式:生成表格形式的输出,调用
mysql_explain_query_expression
。
- 迭代器模式:用于
4. 优化器交互:Query_expression::optimize
(1) 优化流程
Query_expression::optimize
(位于 sql/sql_union.cc
)负责对查询表达式(如 UNION)进行优化:
bool Query_expression::optimize(THD *thd, ...) {
// 1. 优化每个查询块(Query_block)
for (Query_block *query_block = first_query_block(); query_block; query_block = query_block->next_query_block()) {
query_block->optimize(thd, finalize_access_paths);
}
// 2. 处理集合操作(UNION/INTERSECT)
if (!is_simple()) optimize_set_operand(thd, this, query_term());
// 3. 生成访问路径(AccessPath)
create_access_paths(thd);
}
- 核心操作:
- 单查询块优化:为每个
SELECT
子句生成最优执行路径。 - 集合操作处理:优化 UNION 等操作的排序、去重逻辑。
- 访问路径生成:将优化结果封装为
AccessPath
结构,供执行引擎使用。
- 单查询块优化:为每个
5. 特殊场景处理
(1) EXPLAIN ANALYZE
当启用 EXPLAIN ANALYZE
时,源码会 实际执行查询 并收集性能数据:
if (lex->is_explain_analyze) {
Query_result_null null_result;
unit->set_query_result(&null_result); // 抑制结果输出
unit->execute(thd); // 实际执行查询
ExplainIterator(explain_thd, query_thd, unit); // 生成带统计信息的计划
}
- 关键逻辑:通过执行查询获取精确的行数、耗时等指标。
(2) 二级引擎支持
若查询使用二级引擎(如 HeatWave),生成警告并调整执行计划:
if (SecondaryEngineHandlerton(query_thd) != nullptr) {
push_warning(explain_thd, "Query may use secondary engine");
return Explain_secondary_engine(...); // 生成引擎特定的计划
}
6. 执行计划输出示例
(1) 传统格式(TRADITIONAL)
通过 mysql_explain_query_expression
生成表格:
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | t | NULL | ALL | NULL | NULL | NULL | NULL | 1000 | 10.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
(2) JSON 格式
通过 ExplainIterator
生成结构化 JSON:
{
"query_block": {
"select_id": 1,
"cost_info": {"query_cost": "1.20"},
"table": {
"table_name": "t",
"access_type": "ALL",
"rows_examined_per_scan": 1000
}
}
}
7. 调试与扩展
(1) 调试技巧
- GDB 断点:
break opt_explain.cc:explain_query # 跟踪 EXPLAIN 入口 break sql_select.cc:Sql_cmd_dml::execute_inner # 跟踪优化入口
- 日志输出:
// 在 Query_expression::optimize 中打印优化步骤 DBUG_PRINT("info", ("Optimizing query block %d", query_block->select_number));
(2) 扩展性
- 自定义格式:通过继承
Explain_format
类实现新的输出格式。 - 插件优化器:通过
Optimizer
插件接口扩展优化规则。
总结
MySQL 的 EXPLAIN
实现通过 多层模块协作 完成,从命令解析到优化器决策,最终生成用户可读的执行计划。其源码设计兼顾灵活性与性能,支持多种输出格式和复杂查询场景。理解这一机制,有助于深入掌握 MySQL 的查询优化逻辑,并为性能调优提供底层支持。
##gdb调试堆栈
(gdb) bt
#0 explain_query (explain_thd=0x7c292c001070, query_thd=0x7c292c001070, unit=0x7c292cd4cf70) at /home/yym/mysql8/mysql-8.1.0/sql/opt_explain.cc:2242
#1 0x000062de06742cd8 in Sql_cmd_dml::execute_inner (this=0x7c292cd0c590, thd=0x7c292c001070) at /home/yym/mysql8/mysql-8.1.0/sql/sql_select.cc:1020
#2 0x000062de06742067 in Sql_cmd_dml::execute (this=0x7c292cd0c590, thd=0x7c292c001070) at /home/yym/mysql8/mysql-8.1.0/sql/sql_select.cc:793
#3 0x000062de066b4841 in mysql_execute_command (thd=0x7c292c001070, first_level=true) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:4797
#4 0x000062de066b6cb3 in dispatch_sql_command (thd=0x7c292c001070, parser_state=0x7c2a111fd9f0) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:5447
#5 0x000062de066ac0d7 in dispatch_command (thd=0x7c292c001070, com_data=0x7c2a111fe340, command=COM_QUERY) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:2112
#6 0x000062de066a9f77 in do_command (thd=0x7c292c001070) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:1459
#7 0x000062de06901835 in handle_connection (arg=0x62de159f9b90) at /home/yym/mysql8/mysql-8.1.0/sql/conn_handler/connection_handler_per_thread.cc:303
#8 0x000062de08840bdc in pfs_spawn_thread (arg=0x62de159f8fd0) at /home/yym/mysql8/mysql-8.1.0/storage/perfschema/pfs.cc:3043
#9 0x00007c2a1fc94ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#10 0x00007c2a1fd26850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
(gdb) b Query_expression::optimize
Breakpoint 2 at 0x62de067fe9ac: file /home/yym/mysql8/mysql-8.1.0/sql/sql_union.cc, line 983.
##普通select 查询gdb堆栈
(gdb) bt
#0 Query_expression::execute (this=0x7c291070bad0, thd=0x7c2910000bf0) at /home/yym/mysql8/mysql-8.1.0/sql/sql_union.cc:1808
#1 0x000062de06742cf6 in Sql_cmd_dml::execute_inner (this=0x7c29105f3798, thd=0x7c2910000bf0) at /home/yym/mysql8/mysql-8.1.0/sql/sql_select.cc:1022
#2 0x000062de06742067 in Sql_cmd_dml::execute (this=0x7c29105f3798, thd=0x7c2910000bf0) at /home/yym/mysql8/mysql-8.1.0/sql/sql_select.cc:793
#3 0x000062de066b4841 in mysql_execute_command (thd=0x7c2910000bf0, first_level=true) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:4797
#4 0x000062de066b6cb3 in dispatch_sql_command (thd=0x7c2910000bf0, parser_state=0x7c2a0befb9f0) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:5447
#5 0x000062de066ac0d7 in dispatch_command (thd=0x7c2910000bf0, com_data=0x7c2a0befc340, command=COM_QUERY) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:2112
#6 0x000062de066a9f77 in do_command (thd=0x7c2910000bf0) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:1459
#7 0x000062de06901835 in handle_connection (arg=0x62de1335a2f0) at /home/yym/mysql8/mysql-8.1.0/sql/conn_handler/connection_handler_per_thread.cc:303
#8 0x000062de08840bdc in pfs_spawn_thread (arg=0x62de158573a0) at /home/yym/mysql8/mysql-8.1.0/storage/perfschema/pfs.cc:3043
#9 0x00007c2a1fc94ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#10 0x00007c2a1fd26850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
(gdb) p thd->query().str
$16 = 0x7c291070b9f0 "SELECT COUNT(*) FROM yym WHERE DATE_SUB(CURDATE(), INTERVAL 1 DAY)<=create_time\n LIMIT 0, 1000"