掘金 后端 ( ) • 2024-06-13 10:02

1924 年,鲁迅发表了一篇抒情散文《秋夜》。他在开头写道:“在我的后园,可以看见墙外有两株树,一株是枣树,还有一株也是枣树。”

而在 MySQL 的源代码中,如下图所示,INSERT 语句、UPDATE 语句和 DELETE 语句也对应着两棵树,一棵是 PT 树,一棵是 cmd 树

PT_tree_CMD_tree_plainuml

PT 是 Parse Tree 的首字母缩写,Parse Tree(解析树,也称为语法树)是编译器使用的一种数据结构,用于表示源代码的语法结构。

而 cmd 是 command(命令)的缩写。由此可见,MySQL 会将增删改查等 SQL 语句视作命令来加以处理。从位于 SQL 语句处理流程“入口处”的一系列函数的命名上也能看出这一点,SQL 语句到了 MySQL 内部就变成了 command。

handle_connection (arg=0x4d0bf00) at <src>/conn_handler/connection_handler_per_thread.cc:313
  `-- do_command (thd=...) at <src>/sql_parse.cc:1031
        `-- dispatch_command (thd=..., com_data=..., command=COM_QUERY) at <src>/sql_parse.cc:1492
              `-- mysql_parse (thd=..., parser_state=...) at <src>/sql_parse.cc:5584
                    `-- mysql_execute_command (thd=..., first_level=true) at <src>/sql_parse.cc:3617
​

在“入口处”的一系列 SQL 语句处理函数的调用栈中,“command”一词共出现了 3 次

在左侧的 PT 树中,末端带有白色三角的箭头表示继承关系。从类名即可推断出,PT_insert 类、PT_update 类和 PT_delete 类分别对应于 INSERT 语句、UPDATE 语句和 DELETE 语句,这 3 个类都是 PT_statement 类的子类,而 PT_statement 类又是 Parse_tree_node 类的子类。

末端带有黑色菱形的箭头表示拥有(包含)关系。例如,PT_insert 类的对象拥有(包含)Table_ident 类、PT_item_list 类和 PT_insert_values_list 类这 3 个类的对象。

在右侧的 cmd 树中,末端带有白色三角的箭头依然表示继承关系。从类名也依然可以判断出,Sql_cmd_insert 类、Sql_cmd_update 类和 Sql_cmd_delete 类分别对应 INSERT 语句、UPDATE 语句和 DELETE 语句,这 3 个类都是 Sql_cmd_dml 类的子类,而 Sql_cmd_dml 类又是 Sql_cmd 类的子类。

DML(Data Manipulation Language,数据操作语言)是用于查询和修改数据库中数据的一组 SQL 语句的统称。DML语句使用户能够向数据库的表中插入记录、并更新、删除和检索其中的数据。

下面问题来了,既然这两棵树中相对应的节点(如 PT_insert 节点对应 Sql_cmd_insert 节点)都代表着同样的 SQL 语句,那这 3 对节点是怎么关联起来的呢?

或者这样问,SQL 语句是如何变为可执行的命令的?是在哪里变为命令的?

我们注意到,PT_statement 类中有一个名为 make_cmd()纯虚函数,这意味着 PT_statement 类是一个抽象类,不能直接实例化,任何继承了该类的子类必须实现 make_cmd() 方法。而 3 个派生类 PT_insert 类、PT_update 类和 PT_delete 类的确均实现了 make_cmd() 方法,并分别返回了Sql_cmd_insert 类、Sql_cmd_update 类和 Sql_cmd_delete 类的对象。

好了,既然 make_cmd() 方法实现了由字符串的 SQL 语句到可执行的命令的转变,那我们不禁又会好奇,这个方法是在哪里调用的呢?

这个位置并不在 C++ 的源文件中,稍微有点难找。

答案其实在 sql_yacc.yyhttps://github.com/mysql/mysql-server/blob/5.7/sql/sql_yacc.yy#L1675)这个文件中。MySQL 支持的 SQL 语法规则和解析逻辑都定义在该文件中,解析器生成工具 Yacc(或Bison)能够将该文件转换为用 C++ 编写的语法解析器。

statement:
          alter
        ...
        | delete_stmt           {  MAKE_CMD($1); }
        ...
        | insert_stmt           { MAKE_CMD($1); }
        ...
        | update_stmt           { MAKE_CMD($1); }
        ...
        ;

这段代码表示,statement 代表一个 SQL 语句,它可以是多种不同类型的 SQL 语句之一。这里用“...”省略掉无需关注的 SQL 语句。若 SQL 语句是 delete_stmtinsert_stmtupdate_stmt,则调用 MAKE_CMD 这个宏。MAKE_CMD 的定义如下,

// https://github.com/mysql/mysql-server/blob/5.7/sql/sql_yacc.yy#L167
/**
  PT_statement::make_cmd() wrapper to raise postponed error message on OOM

  @note x may be NULL because of OOM error.
*/
#define MAKE_CMD(x)                                     \
  do                                                    \
  {                                                     \
    if (YYTHD->is_error())                              \
      MYSQL_YYABORT;                                    \
    Lex->m_sql_cmd= (x)->make_cmd(YYTHD);               \
  } while(0)

这里的 $1 (对应宏参数 x)就是 PT_insert 类、PT_update 类或 PT_delete 类的对象。


在 MySQL 的源代码中,也有两棵树,一棵是 PT 树,一棵是 cmd 树(这句不像病句)。