mysql 8.0 一条insert语句的具体执行流程分析(一)

最近在mysql 8.0的代码上开发新的功能的时候,梳理了insert语句的执行过程,由于insert语句比较复杂并且涉及的内容很多,在下面准备分3章节来分析,这是第一个章节,主要讲述sql解析和命令的分发部分。

代码版本:mysql 8.0.22

编程语言:c++ && c++11 && c++14 && c++17
 

SQL语句是:

create table test(c1 int primary key, c2 varchar(50))engine=innodb;

insert into test values(1,"abc");

执行分类接口

源码开始位置在/sql/sql_parse.h 和 /sql/sql_parse.cc,sql_parse.cc是一个超大的文件,里面包括了很多个超大的函数,下面按sql执行顺序讲述:

| > do_command
| | > dispatch_command
| | | > mysql_parse(parse_sql)
| | | | > mysql_execute_command --> lex->m_sql_cmd->execute(thd)
| | | | | > thd->send_statement_status() // 检查当前sql的状态

 

1、do_command:从客户端connection到mysql server之后,接收到sql命令的入口

bool do_command(THD *thd) {
  bool return_value;
  int rc;
  NET *net = nullptr;
  enum enum_server_command command;
  COM_DATA com_data;
  DBUG_TRACE;
  DBUG_ASSERT(thd->is_classic_protocol());

  /*
    indicator of uninitialized lex => normal flow of errors handling
    (see my_message_sql)
  */
  thd->lex->set_current_select(nullptr);

  /*
    XXX: this code is here only to clear possible errors of init_connect.
    Consider moving to prepare_new_connection_state() instead.
    That requires making sure the DA is cleared before non-parsing statements
    such as COM_QUIT.
  */
  thd->clear_error();  // Clear error message
  thd->get_stmt_da()->reset_diagnostics_area();
  thd->updated_row_count = 0;
  thd->busy_time = 0;
  thd->cpu_time = 0;
  thd->bytes_received = 0;
  thd->bytes_sent = 0;
  thd->binlog_bytes_written = 0;
  ...
  ...
  return_value = dispatch_command(thd, &com_data, command);
  thd->get_protocol_classic()->get_output_packet()->shrink(
      thd->variables.net_buffer_length);

out:
  /* The statement instrumentation must be closed in all cases. */
  DBUG_ASSERT(thd->m_digest == nullptr);
  DBUG_ASSERT(thd->m_statement_psi == nullptr);
  return return_value;
}

2、dispatch_command: 按照不同的SQL进行分类

    case COM_QUERY: {
      DBUG_ASSERT(thd->m_digest == nullptr);
      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

      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

      const LEX_CSTRING orig_query = thd->query();

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

      // Initially, prepare and optimize the statement for the primary
      // storage engine. If an eligible secondary storage engine is
      // found, the statement may be reprepared for the secondary
      // storage engine later.
      const auto saved_secondary_engine = thd->secondary_engine_optimization();
      thd->set_secondary_engine_optimization(
          Secondary_engine_optimization::PRIMARY_TENTATIVELY);

      mysql_parse(thd, &parser_state, false);
      ...
      ...

3、mysql_parse(parse_sql):从文本字符串中解析输入的SQL,将解析出的AST树传给执行者

void mysql_parse(THD *thd, Parser_state *parser_state, bool update_userstat) {
  DBUG_TRACE;
  DBUG_PRINT("mysql_parse", ("query: '%s'", thd->query().str));

  DBUG_EXECUTE_IF("parser_debug", turn_parser_debug_on(););

  mysql_reset_thd_for_next_command(thd);
  lex_start(thd);

  bool err = thd->get_stmt_da()->is_error();

  if (!err) {
    err = parse_sql(thd, parser_state, nullptr);
  ...

  ...
  error = mysql_execute_command(thd, true);
}

 

4、mysql_execute_command: 按照不同的SQL分类,进入不同的执行流程

switch (lex->sql_command) {
    case SQLCOM_PREPARE: {
      mysql_sql_stmt_prepare(thd);
      break;
    }
    case SQLCOM_EXECUTE: {
      mysql_sql_stmt_execute(thd);
      break;
    }
    case SQLCOM_DEALLOCATE_PREPARE: {
      mysql_sql_stmt_close(thd);
      break;
    }

    ...
    ...
    case SQLCOM_REPLACE:
    case SQLCOM_INSERT:
    case SQLCOM_REPLACE_SELECT:
    case SQLCOM_INSERT_SELECT:
    case SQLCOM_DELETE:
    case SQLCOM_DELETE_MULTI:
    case SQLCOM_UPDATE:
    case SQLCOM_UPDATE_MULTI:
    case SQLCOM_CREATE_TABLE:
    case SQLCOM_CREATE_INDEX:
    case SQLCOM_DROP_INDEX:
    case SQLCOM_ASSIGN_TO_KEYCACHE:
    case SQLCOM_PRELOAD_KEYS:
    case SQLCOM_LOAD: {
      DBUG_ASSERT(first_table == all_tables && first_table != nullptr);
      DBUG_ASSERT(lex->m_sql_cmd != nullptr);
      res = lex->m_sql_cmd->execute(thd);
      break;
    }     
}

数据插入接口

数据插入部分在sql/sql_insert.cc中,执行的顺序如下:

| > Sql_cmd_dml::execute 
| | > write_record  

1、Sql_cmd_dml::execute : 向一个表中插入一行或多行,执行一个DML语句,这里是insert into test values(1,"abc");

bool Sql_cmd_dml::execute(THD *thd) {
  DBUG_TRACE;

  // Perform statement-specific execution
  if (execute_inner(thd)) goto err;
  ...
  ...
}

2、write_record : 在sql_insert.cc中,功能:向表中写入一条记录,可选择删除冲突的记录。

如果需要,调用适当的触发器,参数如下:

THD *thd : 当前thread的上下文 TABLE *table : 该条record要插入到的table COPY_INFO *info : 用来处理唯一键冲突,记录影响行数 COPY_INFO *update : 处理 INSERT ON DUPLICATE KEY UPDATE 相关信息

bool write_record(THD *thd, TABLE *table, COPY_INFO *info, COPY_INFO *update) {
    ...

    store_record(table, insert_values);
    ...
}

 

这一章节主要讲述sql解析、命令的分发、进入innodb存储引擎之前的sql转换等。第二章节主要讲述sql命令传递给innodb之前的一些格式转换和初步的数据插入。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值