从Mysql源代码角度分析一句简单sql的查询过程 - Go语言中文社区

从Mysql源代码角度分析一句简单sql的查询过程


1. 前言

使用mysql这么多年,以前一直只懂写sql,却不其中运行原理,直至最近抽时间看了一下mysql源代码,
对其事务运行原理及sql解析优化有一些更深入的理解.
本篇是讲述sql解析的开篇之作,希望透过最最简单的sql来让大家了解sql的查询解析过程,
如果本文力图把一个简单sql的执行过程所涉及的方法及其相关值的变化详细讲清楚,如果有问题欢迎留言.

2. 准备

2.1 参考

linux下使用eclipse debug mysql5.6

2.2 创建表

create table wlt_tab1(
id int primary key
);

2.3 执行查询

select * from wlt_tab1 where 1=0 and id=0

3. 执行查询

当前sql执行时序图
如果看不清楚,可在PC端点击查看到大图,
下面会针对上面时序图的每个方法详细解说!

3.1 do_handle_one_connection()

如果线程池中没有可用的缓存线程,则会通过本方法创建线程来处理用户请求.

3.2 do_command()

读取客户端传递的命令并分发

3.3 dispatch_command()

根据用户请求信息的第一个字段表示这个请求类型,以下摘取本方法代表性的简化 代码来说明本方法在查询过程中处理了哪些功能

switch(command){
    case COM_INIT_DB: ...;
    case COM_CHANGE_USER: ...;
    case COM_STMT_PREPARE: ...;
    //如果是查询请求
    case COM_QUERY:
    //从网络数据包中读取Query并存入thd->query
    alloc_query(thd,packet,packet_length);
    //解析
    mysql_parse(thd,thd->query(),thd->query_length(),&parser_state);
    ...
}

3.4 mysql_parse()

/*
  从lex_start方法源代码上看,本方法主要是将thd->lex对象内容重新清理
  置为初始化状态.
  注: thd是当前线程上下文信息类,后续与用户处理相关函数都会传入这个类,
  估计是c++没有像java的ThreadLocal那么方便的类,所以老是要这么麻烦地传
  来传去的
  lex: 语法分析对象
  本方法的实现在:sql_lex.cc
*/
lex_start(thd);
/*
查看query cache中是否有命中,如果有,则返回结果
如果没有,则作如下动作
*/
if(query_cache_send_result_to_client(thd,rawbuf,length)<=0){
    //解析sql
    bool err = parse_sql(thd,parser_state,NULL);
    //执行
    mysql_execute_command(thd);
}

parse_sql() sql解析过程

mysql解析过程如下:
sql解析
mysql是使用了开始的bison(即yacc的开源版)作为sql语法解析器
如上图所示,在lex词法解析阶段,会解析出select,from,where这几个token
接下来sql_yacc.cc的MYSQLparse会根据上面的token解析出语法树,yacc是使用巴科斯范式(BNF)表达语法规则,大家可以百度学习一下,下面节选几个与我们相关的表达式:

select_from:
          FROM join_table_list where_clause group_clause having_clause
          opt_order_clause opt_limit_clause procedure_analyse_clause
          {
            Select->context.table_list=
              Select->context.first_name_resolution_table=
                Select->table_list.first;
          }
        | FROM DUAL_SYM where_clause opt_limit_clause
          /* oracle compatibility: oracle always requires FROM clause,
             and DUAL is system table without fields.
             Is "SELECT 1 FROM DUAL" any better than "SELECT 1" ?
          Hmmm :) */
        ;
where_clause:
          /* empty */  { Select->where= 0; }
        | WHERE
          {
            Select->parsing_place= IN_WHERE;
          }
          expr
          {
            SELECT_LEX *select= Select;
            select->where= $3;
            select->parsing_place= NO_MATTER;
            if ($3)
              $3->top_level_item();
          }
        ;

parse_sql()方法执行完后,我们可以在gdb中查看语法树lex:

##查看select_lex->where
(gdb) call print_where(lex->select_lex->where,"",QT_WITHOUT_INTRODUCERS)
WHERE:() 0x7fff98005e10 ((1 = 0) and (`id` = 0))
(gdb) p lex->select_lex->table_list->first
$9 = (TABLE_LIST *) 0x7fff98005260
##查看sql使用的database
(gdb) p $9->db
$10 = 0x7fff980057c0 "wlt"
(gdb) p $9->table_name
$11 = 0x7fff98005218 "wlt_tab1"

3.5 mysql_execute_command()

//获取解析后的sql语法树
Lex *lex = thd->lex;
//根据解析后的sql语法树的类型,决定如何作下一步处理
switch(lex->sql_command){
    case SQLCOM_SHOW_STATUS:...;
    case SQLCOM_INSERT: ...;
    case SQLCOM_SELECT:
        ...
        res = execute_sqlcom_select(thd,all_tables);
}

3.6 execute_sqlcom_select()

3.7 handle_select()

3.8 mysql_select()

sql真正执行入口,
这里会分别执行:

  • JOIN::prepare() ; //预处理
  • JOIN::optimize();//查询优化
  • JOIN::exec();//执行

3.9 JOIN::prepare()

执行sql查询优化计划前的准备工作
其中 setup_wild()方法会把查询语句中的”*”扩展为表上的所有列

3.9.1 setup_wild()

可以看本方法的主要代码:

  while (wild_num && (item= it++))
  {
    if (item->type() == Item::FIELD_ITEM &&
    //如果field值为*
    ((Item_field*) item)->field_name[0] == '*' &&
    !((Item_field*) item)->field)
    {
              if (subsel &&
          subsel->substype() == Item_subselect::EXISTS_SUBS)
      {
      ...
      }else if (insert_fields(thd, ((Item_field*) item)->context,
                             ((Item_field*) item)->db_name,
                             ((Item_field*) item)->table_name, &it,
                             any_privileges))
      {
      ...
      }
    }

3.9.2 insert_fields()

//字段迭代器
Field_iterator_table_ref field_iterator;
field_iterator.set(tables);
for (; !field_iterator.end_of_fields(); field_iterator.next())
    {
      Item *item;
      item= field_iterator.create_item(thd);
      if (!found)
      {
        found= TRUE;
        it->replace(item); 
      }
      else
        it->after(item);   /* 将当前sql语句的表的字段一一加到fields_list中 */
     }

3.10 JOIN::optimize()

JOIN::optimize()函数主要功能是对sql各种优化,包括条件下推,关联索引列,计算最优查询优化执行计划…
与本请求sql优化相关的是optimize_cond()方法
处理本sql时,optimize_cond()方法最终会将select_lex->cond_value置为Item::COND_FALSE,针对这个结果,后续处理如下:

    if (select_lex->cond_value == Item::COND_FALSE || 
        select_lex->having_value == Item::COND_FALSE || 
        (!unit->select_limit_cnt && !(select_options & OPTION_FOUND_ROWS)))
    {                       /* Impossible cond */
      zero_result_cause=  select_lex->having_value == Item::COND_FALSE ?
                           "Impossible HAVING" : "Impossible WHERE";
      tables= 0;
      primary_tables= 0;
      best_rowcount= 0;
      goto setup_subq_exit;
    }

3.10.1 optimize_cond()

这个方法主要代码可以简化如下:

//等式合并
build_equal_items(thd,conds,NULL,true,join_list,cond_equal);
//常量求值
propagate_cond_constants(thd, (I_List<COND_CMP> *) 0, conds, conds);
//条件去除
remove_eq_conds(thd, conds, cond_value) ;

在刚进这个方法时,我们可以打印 conds对象的值

(gdb) p call print_where(conds,"",QT_WITHOUT_INTRODUCERS)
WHERE:() 0x7fff98005e10 ((1 = 0) and (`wlt`.`wlt_tab1`.`id` = 0))

remove_eq_conds()方法会优化掉条件中 1=0

3.10.1.1 remove_eq_conds()

本方法会调用: internal_remove_eq_conds(thd, cond, cond_value); // Scan all the condition

3.10.1.2 internal_remove_eq_conds()

 while ((item=li++))
    {
      /×这里会取当前条件组的第一个条件递归调用本方法
      在递归的方法中会判断到item->const_item()为true,
      并对1=0进行求值:
      *tmp_cond_value= eval_const_cond(cond) ? Item::COND_TRUE : Item::COND_FALSE;
      ×/
      Item *new_item=internal_remove_eq_conds(thd, item, &tmp_cond_value);
            switch (tmp_cond_value) {
      case Item::COND_OK:           // Not TRUE or FALSE
    if (and_level || *cond_value == Item::COND_FALSE)
      *cond_value=tmp_cond_value;
    break;
    //当前1=0的条件会进入 Item::COND_FALSE
      case Item::COND_FALSE:
    if (and_level)
    {
      *cond_value=tmp_cond_value;
      return (Item*) 0;         // Always false
    }
    break;
      }

这里在gdb中如果输入:

(gdb) call print_where(conds,"",QT_WITHOUT_INTRODUCERS)
WHERE:() (nil) 

3.11 JOIN::exec()

  //
  if (zero_result_cause)
  {
    //返回0结果行
    return_zero_rows(this, *columns_list);
    DBUG_VOID_RETURN;
  }

作者: 吴炼钿

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/vipshop_fin_dev/article/details/79688717
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-06-28 00:33:26
  • 阅读 ( 1070 )
  • 分类:数据库

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢