MySQL 一条CRUD指令到达服务器,是如何被执行的

2,120 阅读3分钟

上文中,我们已经从宏观上分析了一个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