# Query入口
MySQL的代码比较messy,为了清楚理解MySQL的已有功能,本篇文章从MySQL的query层面进行分析,目的是理清MySQL的query处理流程和代码架构。本文基于MySQL5.7版本。
MySQL的入口代码的skeloton可以参见官方文档:https://dev.mysql.com/doc/internals/en/guided-tour-skeleton.html
本文着重讲解query parse和optimize的部分。个人认为官方文档对入口代码的说明言简意赅,本文也将采用类似的形式进行代码拆解和分析。
我们首先延续官方文档中给出的入口函数(位于sql_parse.cc下):
```
void mysql_execute_command(THD *thd)
switch (lex->sql_command) {
case SQLCOM_SELECT: ...
case SQLCOM_SHOW_ERRORS: ...
case SQLCOM_CREATE_TABLE: ...
case SQLCOM_UPDATE: ...
case SQLCOM_INSERT: ... // !
case SQLCOM_DELETE: ...
case SQLCOM_DROP_TABLE: ...
case SQLCOM_PREPARE:
{
mysql_sql_stmt_prepare(thd);
break;
}
case SQLCOM_EXECUTE:
{
mysql_sql_stmt_execute(thd);
break;
}
}
```
注意,在MySQL5.7中mysql_execute_command是由mysql_parse调用的。官方文档中调用函数为另一个(mysql_stmt_execute),但他们同为dispatch_command下调用的,所以官方文档仍然可以借鉴。
# Prepare Statement
上述入口代码上我们可以看到两处和Prepare Statement相关的地方mysql_sql_stmt_prepare和mysql_sql_stmt_execute。
## mysql_sql_stmt_prepare
我们先来看看mysql_sql_stmt_prepare做了什么事。如下所示,mysql_sql_stmt_prepare主要做了?件事。
```
stmt->set_name(name);
thd->stmt_map.insert(thd, stmt);
stmt->m_prepared_stmt= MYSQL_CREATE_PS(stmt, stmt->id,
thd->m_statement_psi,
stmt->name().str, stmt->name().length,
query, query_len);
stmt->prepare(query, query_len);
```
这里的MYSQL_CREATE_PS对应了函数inline_mysql_create_prepared_stmt(IDENTITY, ID, LOCKER, NAME, NAME_LENGTH, SQLTEXT, SQLTEXT_LENGTH),其位于include/mysql/psi/mysql_ps.h:
```
inline_mysql_create_prepared_stmt(void *identity, uint stmt_id,
PSI_statement_locker *locker,
const char *stmt_name, size_t stmt_name_length,
const char *sqltext, size_t sqltext_length)
{
if (locker == NULL)
return NULL;
return PSI_PS_CALL(create_prepared_stmt)(identity, stmt_id,
locker,
stmt_name, stmt_name_length,
sqltext, sqltext_length);
}
```
我们可以看到,PSI_PS_CALL对应的执行的函数是create_prepared_stmt,该函数仅仅分配了prepare statement所需的各个数据结构。该函数其位于storage/perfschema/pfs_prepared_stmt.cc。
真正**对query进行parse的是stmt->prepare(query, query_len)**。这个stmt是其声明时定义的Prepared_statement。Prepared_statement这个类中有一个LEX成员变量lex,其保存了parse tree。注意:凡是看到LEX结构,就要想到其对应了parse tree。在源码中也指明了它是parse tree descriptor。prepare statement最重要的调用是parse_sql(thd, &parser_state, NULL)来解析sql语句生成parse tree。parse_sql调用的是sql_yacc.cc中的MYSQLparse来做的sql解析。
**注意,最后的结果都是在THD中。任何的数据结构想要访问query解析的结果,其都应该包含THD,通过THD来访问。**
## mysql_sql_stmt_execute
```
void mysqld_stmt_execute(THD *thd, ulong stmt_id, ulong flags, uchar *params,
ulong params_length)
{
Prepared_statement *stmt;
stmt->execute_loop(&expanded_query, open_cursor, params,
params + params_length);
}
```
这里列出了最重要的内容,即执行函数execute_loop。代码中对exectue_loop的解释如下:
```
/**
Execute a prepared statement. Re-prepare it a limited number
of times if necessary.
Try to execute a prepared statement. If there is a metadata
validation error, prepare a new copy of the prepared statement,
swap the old and the new statements, and try again.
If there is a validation error again, repeat the above, but
perform no more than MAX_REPREPARE_ATTEMPTS.
@note We have to try several times in a loop since we
release metadata locks on tables after prepared statement
prepare. Therefore, a DDL statement may sneak in between prepare
and execute of a new statement. If this happens repeatedly
more than MAX_REPREPARE_ATTEMPTS times, we give up.
@return TRUE if an error, FALSE if success
@retval TRUE either MAX_REPREPARE_ATTEMPTS has been reached,
or some general error
@retval FALSE successfully executed the statement, perhaps
after having reprepared it a few times.
*/
Prepared_statement::execute_loop(String *expanded_query,
bool open_cursor,
uchar *packet,
uchar *packet_end)
{
...
reexecute:
error= execute(expanded_query, open_cursor) || thd->is_error();
if (error) {
MySQL的代码比较messy,为了清楚理解MySQL的已有功能,本篇文章从MySQL的query层面进行分析,目的是理清MySQL的query处理流程和代码架构。本文基于MySQL5.7版本。
MySQL的入口代码的skeloton可以参见官方文档:https://dev.mysql.com/doc/internals/en/guided-tour-skeleton.html
本文着重讲解query parse和optimize的部分。个人认为官方文档对入口代码的说明言简意赅,本文也将采用类似的形式进行代码拆解和分析。
我们首先延续官方文档中给出的入口函数(位于sql_parse.cc下):
```
void mysql_execute_command(THD *thd)
switch (lex->sql_command) {
case SQLCOM_SELECT: ...
case SQLCOM_SHOW_ERRORS: ...
case SQLCOM_CREATE_TABLE: ...
case SQLCOM_UPDATE: ...
case SQLCOM_INSERT: ... // !
case SQLCOM_DELETE: ...
case SQLCOM_DROP_TABLE: ...
case SQLCOM_PREPARE:
{
mysql_sql_stmt_prepare(thd);
break;
}
case SQLCOM_EXECUTE:
{
mysql_sql_stmt_execute(thd);
break;
}
}
```
注意,在MySQL5.7中mysql_execute_command是由mysql_parse调用的。官方文档中调用函数为另一个(mysql_stmt_execute),但他们同为dispatch_command下调用的,所以官方文档仍然可以借鉴。
# Prepare Statement
上述入口代码上我们可以看到两处和Prepare Statement相关的地方mysql_sql_stmt_prepare和mysql_sql_stmt_execute。
## mysql_sql_stmt_prepare
我们先来看看mysql_sql_stmt_prepare做了什么事。如下所示,mysql_sql_stmt_prepare主要做了?件事。
```
stmt->set_name(name);
thd->stmt_map.insert(thd, stmt);
stmt->m_prepared_stmt= MYSQL_CREATE_PS(stmt, stmt->id,
thd->m_statement_psi,
stmt->name().str, stmt->name().length,
query, query_len);
stmt->prepare(query, query_len);
```
这里的MYSQL_CREATE_PS对应了函数inline_mysql_create_prepared_stmt(IDENTITY, ID, LOCKER, NAME, NAME_LENGTH, SQLTEXT, SQLTEXT_LENGTH),其位于include/mysql/psi/mysql_ps.h:
```
inline_mysql_create_prepared_stmt(void *identity, uint stmt_id,
PSI_statement_locker *locker,
const char *stmt_name, size_t stmt_name_length,
const char *sqltext, size_t sqltext_length)
{
if (locker == NULL)
return NULL;
return PSI_PS_CALL(create_prepared_stmt)(identity, stmt_id,
locker,
stmt_name, stmt_name_length,
sqltext, sqltext_length);
}
```
我们可以看到,PSI_PS_CALL对应的执行的函数是create_prepared_stmt,该函数仅仅分配了prepare statement所需的各个数据结构。该函数其位于storage/perfschema/pfs_prepared_stmt.cc。
真正**对query进行parse的是stmt->prepare(query, query_len)**。这个stmt是其声明时定义的Prepared_statement。Prepared_statement这个类中有一个LEX成员变量lex,其保存了parse tree。注意:凡是看到LEX结构,就要想到其对应了parse tree。在源码中也指明了它是parse tree descriptor。prepare statement最重要的调用是parse_sql(thd, &parser_state, NULL)来解析sql语句生成parse tree。parse_sql调用的是sql_yacc.cc中的MYSQLparse来做的sql解析。
**注意,最后的结果都是在THD中。任何的数据结构想要访问query解析的结果,其都应该包含THD,通过THD来访问。**
## mysql_sql_stmt_execute
```
void mysqld_stmt_execute(THD *thd, ulong stmt_id, ulong flags, uchar *params,
ulong params_length)
{
Prepared_statement *stmt;
stmt->execute_loop(&expanded_query, open_cursor, params,
params + params_length);
}
```
这里列出了最重要的内容,即执行函数execute_loop。代码中对exectue_loop的解释如下:
```
/**
Execute a prepared statement. Re-prepare it a limited number
of times if necessary.
Try to execute a prepared statement. If there is a metadata
validation error, prepare a new copy of the prepared statement,
swap the old and the new statements, and try again.
If there is a validation error again, repeat the above, but
perform no more than MAX_REPREPARE_ATTEMPTS.
@note We have to try several times in a loop since we
release metadata locks on tables after prepared statement
prepare. Therefore, a DDL statement may sneak in between prepare
and execute of a new statement. If this happens repeatedly
more than MAX_REPREPARE_ATTEMPTS times, we give up.
@return TRUE if an error, FALSE if success
@retval TRUE either MAX_REPREPARE_ATTEMPTS has been reached,
or some general error
@retval FALSE successfully executed the statement, perhaps
after having reprepared it a few times.
*/
Prepared_statement::execute_loop(String *expanded_query,
bool open_cursor,
uchar *packet,
uchar *packet_end)
{
...
reexecute:
error= execute(expanded_query, open_cursor) || thd->is_error();
if (error) {