MySQL源码学习——USE语句的秘密
Louis Hust
0 Preface
最近一个项目需要解析MySQL的通信协议,这时候便碰到了USE语句的解析,发现客户端 mysql发送到服务器端的USE语句对应的并不是SQLCOM_CHANGE_DB命令,而是COM_INIT_DB。 而且这两个命令的处理逻辑基本一致,都是调用mysql_change_db进行处理,那么什么时候 发送COM_INIT_DB,什么时候发送SQLCOM_CHANGE_DB命令呢?这便是本文需要解释的地方。
0 COM_INIT_DB 与 SQLCOM_CHANGE_DB
首先解释下这两个命令,其实这两个命令不是在一个层次的,COM_INIT_DB命令是最高层次的命令, SQLCOM_CHANGE_DB属于COM_QUERY中的一个子命令,属于一个低层次的命令。那么何为高层次的命令, 什么又是低层次的命令么?
客户端发过来的命令均是高层次的命令,即均是COM开头的命令,区别不同的高层次命令是由数据包中的第 4个字节(包含第0字节)进行确定(即跳过packet header四个字节后的第一个字节)。COM_INIT_DB在 第4个字节上使用0x02,COM_QUERY使用0x03.
SQLCOM_CHANGE_DB命令属于COM_QUERY的一个子命令,客户端发送过来的只是一个QUERY,并不知道这个 QUERY是要CHANGE DB还是要做SELECT或者其他操作,这就需要服务器端进行语法解析,即所谓的YACC语法解析。 经过解析后才能知道这条命令到底是什么,这就是低层次的命令。只有COM_QUERY这类高层命令需要解析出低层 命令。
0 什么时候发送COM_INIT_DB?
其实直接使用mysql客户端执行use语句的时候,你会发现,服务器端执行的是COM_INIT_DB命令, 而不是直接是COM_QUERY,这说明客户端一定对语句做了一些处理,如果不处理的话,所有输入客户端的 命令,mysql应该都认为是COM_QUERY,而事实并非如此。
那我们跟踪一下代码就会发现,确实mysql客户端进行了一些预处理工作。下面先给出一个客户端 USE语句的跟踪堆栈。
mysql> use mysql (gdb) bt #0 com_use (buffer=0x93c0c0 <glob_buffer>, line=0x9f5570 "use mysql") at /home/loushuai/src/mysql/hust-mysql/client/mysql.cc:4118 #1 0x000000000040c619 in read_and_execute (interactive=true) at /home/loushuai/src/mysql/hust-mysql/client/mysql.cc:1932 #2 0x000000000040b657 in main (argc=8, argv=0x948418) at /home/loushuai/src/mysql/hust-mysql/client/mysql.cc:1230
从堆栈可以看出,这里调用了com_use函数专门处理USE语句。下面给出调用的伪代码。
main { ... read_and_exeucte() ... } read_and_execute { ... for (;;) { ... /**读取命令 */ line= readline(prompt); ... if ((named_cmds || glob_buffer.is_empty()) && !ml_comment && !in_string && (com=find_command(line,0))) { /** 如果find到command, 则执行相应的func */ if ((*com->func)(&glob_buffer,line) > 0) break; } } ... } find_command函数即在预定义的commands数组中查找相应的命令。 static COMMANDS commands[] = { { "?", '?', com_help, 1, "Synonym for `help'." }, { "clear", 'c', com_clear, 0, "Clear the current input statement."}, { "connect",'r', com_connect,1, "Reconnect to the server. Optional arguments are db and host." }, { "delimiter", 'd', com_delimiter, 1, "Set statement delimiter." }, #ifdef USE_POPEN { "edit", 'e', com_edit, 0, "Edit command with $EDITOR."}, #endif { "ego", 'G', com_ego, 0, "Send command to mysql server, display result vertically."}, { "exit", 'q', com_quit, 0, "Exit mysql. Same as quit."}, { "go", 'g', com_go, 0, "Send command to mysql server." }, { "help", 'h', com_help, 1, "Display this help." }, #ifdef USE_POPEN { "nopager",'n', com_nopager,0, "Disable pager, print to stdout." }, #endif
0 什么时候发送SQLCOM_CHANGE_DB?
这个命令的发送比较简单,只要不通过mysql客户端即可。可以通过一些Connector以Query的方式 进行执行,如C API提供的mysql_query函数,mysql_query(conn, üse test");
顺便说一下,mysql test框架中的测试用例的解析(调用mysqltest进行解析), 是直接使用的C的API进行处理的,所以不会 出现mysql客户端中预处理的情况。即USE TEST会被解析未SQLCOM_CHANGE_DB命令。
File translated from
TEX
by
TTH,
version 4.03.
On 30 Apr 2013, 19:42.