MySQL指令处理流程简析

1,588 阅读3分钟

1 先来张图

查询的流程(图片来自《MySQL 是怎样运行的:从根儿上理解 MySQL》)。本文我们主要涉及解析与优化,以及执行的过程。

2 do_command 流程简析

如上文 MySQL启动流程与连接简析 所述,指令的处理在do_command 中。 那么do_command 中做了什么呢?本文简单讲解一下。

bool do_command(THD *thd)
{
  ...

  if (classic)
  {
    /*
      This thread will do a blocking read from the client which
      will be interrupted when the next command is received from
      the client, the connection is closed or "net_wait_timeout"
      number of seconds has passed.
    */
    // 如英文注释所示,线程将会从客户端中监听指令。
    net= thd->get_protocol_classic()->get_net();
    my_net_set_read_timeout(net, thd->variables.net_wait_timeout);
    net_new_transaction(net);
  }
  ...
  // 从网络中读取
  if (classic)
    my_net_set_read_timeout(net, thd->variables.net_read_timeout);
  // dispatch_command 是重点,下面再分析。 
  return_value= dispatch_command(thd, &com_data, command);
  thd->get_protocol_classic()->get_packet()->shrink(
      thd->variables.net_buffer_length);

...
}

如代码段所示,do_command 的主要用途是读取网络连接中client传递过来的指令,然后解析,最后交给dispatch_command 处理。 下面我们再分析一下dispatch_command 的主要流程。

3 dispatch_command 流程分析

bool dispatch_command(THD *thd, const COM_DATA *com_data,
                      enum enum_server_command command)
{
  ...
  thd->set_command(command);
  ...
  // 使用 lex 进行sql command 解析。
  thd->lex->sql_command= SQLCOM_END; /* to avoid confusing VIEW detectors */
  ...
  thd->set_query_id(next_query_id());
  thd->rewritten_query.mem_free();
  thd_manager->inc_thread_running();

 ...

 // 权限校验
  if (unlikely(thd->security_context()->password_expired() &&
               command != COM_QUERY &&
               command != COM_STMT_CLOSE &&
               command != COM_STMT_SEND_LONG_DATA &&
               command != COM_PING &&
               command != COM_QUIT))
  {
    my_error(ER_MUST_CHANGE_PASSWORD, MYF(0));
    goto done;
  }

#ifndef EMBEDDED_LIBRARY
  if (mysql_audit_notify(thd,
                         AUDIT_EVENT(MYSQL_AUDIT_COMMAND_START),
                         command, command_name[command].str))
  {
    goto done;
  }
#endif /* !EMBEDDED_LIBRARY */
  // 下面进入重点,分别处理各种指令。
  switch (command) {
  ...
  case COM_QUERY:
  {
    // 查询相关。
    DBUG_ASSERT(thd->m_digest == NULL);
    thd->m_digest= & thd->m_digest_state;
    thd->m_digest->reset(thd->m_token_array, max_digest_length);

    if (alloc_query(thd, com_data->com_query.query,
                    com_data->com_query.length))
      break;					// fatal error is set
    MYSQL_QUERY_START(const_cast<char*>(thd->query().str), thd->thread_id(),
                      (char *) (thd->db().str ? thd->db().str : ""),
                      (char *) thd->security_context()->priv_user().str,
                      (char *) thd->security_context()->host_or_ip().str);

    const char *packet_end= thd->query().str + thd->query().length;

    if (opt_general_log_raw)
      query_logger.general_log_write(thd, command, thd->query().str,
                                     thd->query().length);

    DBUG_PRINT("query",("%-.4096s", thd->query().str));

#if defined(ENABLED_PROFILING)
    thd->profiling.set_query_source(thd->query().str, thd->query().length);
#endif

    Parser_state parser_state;
    if (parser_state.init(thd, thd->query().str, thd->query().length))
      break;

    mysql_parse(thd, &parser_state);

    while (!thd->killed && (parser_state.m_lip.found_semicolon != NULL) &&
           ! thd->is_error())
    {
      /*
        Multiple queries exits, execute them individually
      */
      const char *beginning_of_next_stmt= parser_state.m_lip.found_semicolon;

      /* Finalize server status flags after executing a statement. */
      thd->update_server_status();
      thd->send_statement_status();
      query_cache.end_of_result(thd);

#ifndef EMBEDDED_LIBRARY
      mysql_audit_notify(thd, AUDIT_EVENT(MYSQL_AUDIT_GENERAL_STATUS),
                         thd->get_stmt_da()->is_error() ?
                         thd->get_stmt_da()->mysql_errno() : 0,
                         command_name[command].str,
                         command_name[command].length);
#endif

      size_t length= static_cast<size_t>(packet_end - beginning_of_next_stmt);

      log_slow_statement(thd);

      /* Remove garbage at start of query */
      while (length > 0 && my_isspace(thd->charset(), *beginning_of_next_stmt))
      {
        beginning_of_next_stmt++;
        length--;
      }

/* PSI end */
      MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
      thd->m_statement_psi= NULL;
      thd->m_digest= NULL;

/* DTRACE end */
      if (MYSQL_QUERY_DONE_ENABLED())
      {
        MYSQL_QUERY_DONE(thd->is_error());
      }

/* SHOW PROFILE end */
#if defined(ENABLED_PROFILING)
      thd->profiling.finish_current_query();
#endif

/* SHOW PROFILE begin */
#if defined(ENABLED_PROFILING)
      thd->profiling.start_new_query("continuing");
      thd->profiling.set_query_source(beginning_of_next_stmt, length);
#endif

/* DTRACE begin */
      MYSQL_QUERY_START(const_cast<char*>(beginning_of_next_stmt),
                        thd->thread_id(),
                        (char *) (thd->db().str ? thd->db().str : ""),
                        (char *) thd->security_context()->priv_user().str,
                        (char *) thd->security_context()->host_or_ip().str);

/* PSI begin */
      thd->m_digest= & thd->m_digest_state;
      thd->m_digest->reset(thd->m_token_array, max_digest_length);

      thd->m_statement_psi= MYSQL_START_STATEMENT(&thd->m_statement_state,
                                          com_statement_info[command].m_key,
                                          thd->db().str, thd->db().length,
                                          thd->charset(), NULL);
      THD_STAGE_INFO(thd, stage_starting);

      thd->set_query(beginning_of_next_stmt, length);
      thd->set_query_id(next_query_id());
      /*
        Count each statement from the client.
      */
      thd->status_var.questions++;
      thd->set_time(); /* Reset the query start time. */
      parser_state.reset(beginning_of_next_stmt, length);
      /* TODO: set thd->lex->sql_command to SQLCOM_END here */
      mysql_parse(thd, &parser_state);
    }

    /* Need to set error to true for graceful shutdown */
    if((thd->lex->sql_command == SQLCOM_SHUTDOWN) && (thd->get_stmt_da()->is_ok()))
      error= TRUE;

    DBUG_PRINT("info",("query ready"));
    break;
  }
  ...
// 下面是一条指令结束之后的操作。
done:
  ...
  /* Finalize server status flags after executing a command. */
  thd->update_server_status();
  if (thd->killed)
    thd->send_kill_message();
  thd->send_statement_status();
  thd->rpl_thd_ctx.session_gtids_ctx().notify_after_response_packet(thd);
  query_cache.end_of_result(thd);



  log_slow_statement(thd);

  THD_STAGE_INFO(thd, stage_cleaning_up);

  thd->reset_query();
  thd->set_command(COM_SLEEP);
  thd->proc_info= 0;
  thd->lex->sql_command= SQLCOM_END;

  /* Performance Schema Interface instrumentation, end */
  MYSQL_END_STATEMENT(thd->m_statement_psi, thd->get_stmt_da());
  thd->m_statement_psi= NULL;
  thd->m_digest= NULL;

  /* Prevent rewritten query from getting "stuck" in SHOW PROCESSLIST. */
  thd->rewritten_query.mem_free();

  thd_manager->dec_thread_running();
  free_root(thd->mem_root,MYF(MY_KEEP_PREALLOC));

  ...
}

代码很长,其实核心就是:

  • 解析sql
  • 权限校验
  • 执行sql
  • 执行后善后操作。

代码中,switch case 中有好多类型的指令。指令的枚举类:

4 总结

本文简单的分析了指令处理的过程,下一篇文章中,我们将看下COM_QUERY这个指令的流程,顺便看下其中涉及编码的部分。

5 参考文献

写完文章,发现这篇文章讲的很体系,也放这儿供以后查阅。 www.cnblogs.com/end/archive…