您的位置:首页 > 数据库 > MySQL

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

2018-03-26 00:40 513 查看

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. 执行查询



如果看不清楚,可在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解析过程如下:



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;
}


作者: 吴炼钿
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息