MySQL 语法解析

2020-11-21 database mysql

当服务器接收到一条 SQL 语句时,会经过词法分析、语法分析、语义分析、构造执行树、生成执行计划、计划的执行几个过程。其中,词法语法解析的处理过程根编译原理上的东西基本类似,MySQL 并没有使用 lex 来实现词法分析,但是语法分析用了 yacc。

与之对比的 SQLite 数据库,其词法分析器是手工写的,语法分析器由 Lemon 生成。在此介绍其在 MySQL 中的使用。

源码解析

Linux 一般来说,词法和语法解析都是通过 Flex 与 Bison 完成的;而在 MySQL 中,词法分析使用自己的程序,而语法分析使用的是 Bison;Bison 会根据 MySQL 定义的语法规则,进行语法解析。

完成语法解析后,会将解析结果生成的数据结构保存在 struct LEX 中,该结构体在 sql/sql_lex.h 文件中定义。

struct LEX: public Query_tables_list
{
  friend bool lex_start(THD *thd);

  SELECT_LEX_UNIT *unit;                 ///< Outer-most query expression
  /// @todo: select_lex can be replaced with unit->first-select()
  SELECT_LEX *select_lex;                ///< First query block
  SELECT_LEX *all_selects_list;          ///< List of all query blocks
private:
  /* current SELECT_LEX in parsing */
  SELECT_LEX *m_current_select;
  ... ...
}

优化器会根据这里的数据,生成相应的执行计划,最后调用存储引擎执行。

执行过程

以下是语法解析模块掉用过程。

mysql_parse()
 |-mysql_reset_thd_for_next_command()
 |-lex_start()
 |-query_cache_send_result_to_client()    #  首先查看cache
 |-parse_sql()                            #  MYSQLparse的外包函数
   |-MYSQLparse()                         #  实际的解析函数入口

如上,SQL 解析入口会调用 MYSQLparse,而在 sql/sql_yacc.cc 中有如下的宏定义,也就说,在预编译阶段,会将 yyparse 替换为 MYSQLparse,所以 实际调用的仍是 yyparse 函数

#define yyparse         MYSQLparse

记下来详细介绍其实现细节。

词法解析

MYSQL 的词法分析并没有使用 LEX,而是有自己的一套词法分析,代码详见 sql/sql_lex.cc 中的实现,其入口函数是 MYSQLlex()

int MYSQLlex(YYSTYPE *yylval, YYLTYPE *yylloc, THD *thd)
{
  ... ...
  token= lex_one_token(yylval, thd);
  yylloc->cpp.start= lip->get_cpp_tok_start();
  yylloc->raw.start= lip->get_tok_start();

  switch(token) {
  case WITH:
    /*
      Parsing 'WITH' 'ROLLUP' or 'WITH' 'CUBE' requires 2 look ups,
      which makes the grammar LALR(2).
      Replace by a single 'WITH_ROLLUP' or 'WITH_CUBE' token,
      to transform the grammar into a LALR(1) grammar,
      which sql_yacc.yy can process.
    */
    token= lex_one_token(yylval, thd);
    switch(token) {
    case CUBE_SYM:
      yylloc->cpp.end= lip->get_cpp_ptr();
      yylloc->raw.end= lip->get_ptr();
      lip->add_digest_token(WITH_CUBE_SYM, yylval);
      return WITH_CUBE_SYM;
    case ROLLUP_SYM:
      yylloc->cpp.end= lip->get_cpp_ptr();
      yylloc->raw.end= lip->get_ptr();
      lip->add_digest_token(WITH_ROLLUP_SYM, yylval);
      return WITH_ROLLUP_SYM;
    default:
      /*
        Save the token following 'WITH'
      */
      lip->lookahead_yylval= lip->yylval;
      lip->yylval= NULL;
      lip->lookahead_token= token;
      yylloc->cpp.end= lip->get_cpp_ptr();
      yylloc->raw.end= lip->get_ptr();
      lip->add_digest_token(WITH, yylval);
      return WITH;
    }
    break;
  }
  ... ...
}

语法分析

Bison 和词法分析的函数接口是 yylex(),在需要的时候掉用 yylex() 获取词法解析的数据,并完成自己的语法解析。

正常来说,Bison 的实际入口函数应该是 yyparse(),而在 MySQL 中通过宏定义,将 yyparse() 替换为 MYSQLParse(),如上所述,实际调用的仍然是 yyparse()

另外,我们可以根据 Bison 中的 Action 操作来查看 MySQL 解析结果的存储结构。

调试

在这里通过考察存储的 WHERE 数据结构来查看语法解析的结果。

(gdb) attach PID
(gdb) set print pretty on                                  # 设置显示样式
(gdb) b mysql_execute_command                              # 可以用来查看所有的SQL
(gdb) p thd->lex->select_lex
(gdb) p ((Item_cond*)thd->lex->select_lex->where)->list    # 查看WHERE中的list
(gdb) detach