上文中,我们已经从宏观上分析了一个sql 指令从客户端到达服务器时,都是怎么被执行的。这篇文章我们将分析一个最常用的指令query(INSERT、SELECT、UPDATE、DELETE 等 SQL 归属于COM_QUERY。) 的执行过程。
1 com query 的核心流程
那我们来分析下COM_QUERY。 代码有些长,我们只把核心的流程留下。
case COM_QUERY:
{
...
// 读取指令,并存放在thd->query 中。
if (alloc_query(thd, com_data->com_query.query,
com_data->com_query.length))
break; // fatal error is set
...
// 解析并执行sql。 可能有多条语句一起执行,以‘;’ 分隔。里面也涉及到query_cache 相关的内容,后面我们再分析。
mysql_parse(thd, &parser_state);
while (!thd->killed && (parser_state.m_lip.found_semicolon != NULL) &&
! thd->is_error())
{
...
/* Finalize server status flags after executing a statement. */
thd->update_server_status();
thd->send_statement_status();
query_cache.end_of_result(thd);
// 慢查询相关的操作
log_slow_statement(thd);
...
// 批量处理,没处理完,继续下一条指令的处理。
mysql_parse(thd, &parser_state);
}
...
break;
}
如 代码所示,com query 指令就是读取请求指令,然后使用mysql_parse 进行逐条解析与执行。其核心就是mysql_parse. 下面我们分析下mysql_parse 函数。
2 mysql_parse 流程分析
废话先不说,先看代码。
void mysql_parse(THD *thd, Parser_state *parser_state)
{
...
// 准备语法解析。
lex_start(thd);
thd->m_parser_state= parser_state;
invoke_pre_parse_rewrite_plugins(thd);
thd->m_parser_state= NULL;
enable_digest_if_any_plugin_needs_it(thd, parser_state);
// 这儿分了是否命中query_cache的逻辑,我们着重看下没有命中的情况。
if (query_cache.send_result_to_client(thd, thd->query()) <= 0)
{
LEX *lex= thd->lex;
...
if (!err)
{
// 解析sql 。
err= parse_sql(thd, parser_state, NULL);
if (!err)
err= invoke_post_parse_rewrite_plugins(thd, false);
found_semicolon= parser_state->m_lip.found_semicolon;
}
if (!err)
{
// 解析完,会有rewrite的过程,这个会在以后的文章中介绍。
mysql_rewrite_query(thd);
...
// 实际执行。
error= mysql_execute_command(thd, true);
}
else
{
// 命中query cache的逻辑。
if (!opt_general_log_raw)
query_logger.general_log_write(thd, COM_QUERY, thd->query().str,
thd->query().length);
parser_state->m_lip.found_semicolon= NULL;
}
DEBUG_SYNC(thd, "query_rewritten");
DBUG_VOID_RETURN;
}
mysql_parse 主要的功能就是解析语法,然后rewrite, 最后执行。那我们看下 mysql_execute_command。
3 mysql_execute_command
这个函数3000多行(按照编码规范,这需要重构。。。),我们就捡重要的说吧。
mysql_execute_command(THD *thd, bool first_level)
{
...
// 针对不同的sql command 进行不同的处理。
switch (lex->sql_command) {
...
// 有无数的case , 我们就挑一个最常用的select看下吧。
case SQLCOM_SELECT:
{
// 预处理与检查。 检查主要是检查权限。
res= select_precheck(thd, lex, all_tables, first_table);
// 执行
if (!res)
res= execute_sqlcom_select(thd, all_tables);
// 记录
thd->save_current_query_costs();
break;
}
...
}
mysql_execute_command 虽然代码比较多,但是结构很清晰,拿到执行,然后不同的command做不同的处理。那我们再看下常用的select 操作中的核心函数 execute_sqlcom_select。
4 execute_sqlcom_select
static bool execute_sqlcom_select(THD *thd, TABLE_LIST *all_tables)
{
LEX *lex= thd->lex;
bool statement_timer_armed= false;
bool res;
// limit限制相关。如果没有自带过来,则取系统的默认值。
{
SELECT_LEX *param= lex->unit->global_parameters();
if (!param->explicit_limit)
param->select_limit=
new Item_int((ulonglong) thd->variables.select_limit);
}
//计时器
if (is_timer_applicable_to_statement(thd))
statement_timer_armed= set_statement_timer(thd);
// 打开所有需要的table。
if (!(res= open_tables_for_query(thd, all_tables, 0)))
{
MYSQL_SELECT_START(const_cast<char*>(thd->query().str));
// 这儿区分是否是explain 的操作。
if (lex->is_explain())
{
Query_result *const result= new Query_result_send;
if (!result)
return true; /* purecov: inspected */
// 执行query
res= handle_query(thd, lex, result, 0, 0);
}
else
{
Query_result *result= lex->result;
if (!result && !(result= new Query_result_send()))
return true; /* purecov: inspected */
Query_result *save_result= result;
Query_result *analyse_result= NULL;
if (lex->proc_analyse)
{
if ((result= analyse_result=
new Query_result_analyse(result, lex->proc_analyse)) == NULL)
return true;
}
// 执行query
res= handle_query(thd, lex, result, 0, 0);
delete analyse_result;
if (save_result != lex->result)
delete save_result;
}
MYSQL_SELECT_DONE((int) res, (ulong) thd->current_found_rows);
}
if (statement_timer_armed && thd->timer)
reset_statement_timer(thd);
DEBUG_SYNC(thd, "after_table_open");
return res;
}
这个过程不是很复杂,直接看代码注释就好。
5 总结
本文分析了com query 的核心流程,其中包含了解析与执行。在执行时,源码中又区分了好多种情况,我们只是简单列了一下最最常用的select 操作。
通过上面几篇文章,我们总算是分析完了MySQL 从编译、安装,到服务器启动,然后到客户端与服务端建立连接,指令传输,以及指令处理的过程。 后面将会针对特定的问题,进行特定代码的分析。争取从源码上了解我们日常开发的一些常见问题,加油。
6 参考文献
MySQL源码5.7