最近在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之前的一些格式转换和初步的数据插入。