最近在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 statementssuch as COM_QUIT.*/thd->clear_error(); // Clear error messagethd->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 setconst 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);
#endifconst 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 executionif (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之前的一些格式转换和初步的数据插入。