INSERT、UPDATE 和 DELETE 语句在 MySQL 的源代码中是两棵树
1924 年,鲁迅发表了一篇抒情散文《秋夜》。他在开头写道:“在我的后园,可以看见墙外有两株树,一株是枣树,还有一株也是枣树。”
而在 MySQL 的源代码中,如下图所示,INSERT
语句、UPDATE
语句和 DELETE
语句也对应着两棵树,一棵是 PT 树,一棵是 cmd 树。
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.yy
(github.com/mysql/mysql…)这个文件中。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_stmt
、insert_stmt
或 update_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 树(这句不像病句)。
转载自:https://juejin.cn/post/7379166365972365322