代码改变世界

MySQL源码学习——USE语句的秘密

2013-04-30 19:45  心中无码  阅读(1743)  评论(0编辑  收藏  举报


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.