最近在MySQL中实现了Oracle/PostgreSQL中UPDATE…RETURNING…语法,实现过程中详细分析了update语法SQL解析过程,博客中记一笔
mysql_update调用过程
执行一条update语句的入口,函数参数:
int mysql_update(THD *thd,TABLE_LIST *tables,List<Item> &fields, List<Item> &values,COND *conds, uint order_num, ORDER *order, ha_rows limit, enum enum_duplicates handle_duplicates, bool ignore, ha_rows *updated_return); |
传入参数比较多,重点介绍其中几个:
fields:set左值集合,即需要set的字段集合
values:set右值集合,与fields中的字段一一对应
conds:where字段,想详细了解这部分可以参考orczhou的一篇博客
found_return,found_return:这两个是输出参数,返回根据where条件找到的记录数和实际发生更新的记录数
从mysql_update被调用的地方mysql_execute_command,观察这些参数是从什么地方来的:
res= (up_result= mysql_update(thd, all_tables, select_lex->item_list, lex->value_list, select_lex->where, select_lex->order_list.elements, select_lex->order_list.first, unit->select_limit_cnt, lex->duplicates, lex->ignore, &found, &updated)); |
fields <- thd->lex->select_lex.item_list
values <- thd->lex->values_list
conds <- thd->lex->select_lex.where
实际上,这些参数都来自:thd->lex,MySQL支持的所有支持的SQL/存储过程/command在通过lex和yacc分析后都保存在这个结构中,这篇博客只关注与UPDATE语法相关的
update sql解析过程
解析入口:
dispatch_command |-->mysql_parse |-->parse_sql |-->MYSQLparse |
在sql/sql_yacc.cc中
#define yyparse MYSQLparse |
看到yyparse应该有些眉目了,mysql_parse解析的对象是单条SQL, 若SQL为multi-statement,会以分号解析成多条SQL多次调用mysql_parse,调用parse_sql之前都会先调用lex_start清理内存,解析规则是在sql/sql_yacc.yy中定义,sql_yacc.yy文件被gcc编译后会自动生成对应的sql_yacc.cc文件(这个定义了一个可执行的自动状态机,代码基本不可读)
sql/sql_yacc.yy
定义了一系列复杂的规则,需要关注两个规则:
query:所有的statement都会规约到这个规则
statement:MySQL支持的各种SQL定义,如果要添加一种SQL语法,需要在statement规则中增加一行,并在sql_yacc.yy文件中定义相应的规则描述
一种简单方法查找一条SQL对应的语法规则定义:通过thd->lex_sql_command确定SQL类型,然后在sql_yacc.yy中grep一下就OK了
在sql_yacc.yy中查看statement中update规则的定义
/* Update rows in a table */ update: UPDATE_SYM { LEX *lex= Lex; mysql_init_select(lex); lex->sql_command= SQLCOM_UPDATE; lex->duplicates= DUP_ERROR; } opt_low_priority opt_ignore join_table_list SET update_list { LEX *lex= Lex; if (lex->select_lex.table_list.elements > 1) lex->sql_command= SQLCOM_UPDATE_MULTI; else if (lex->select_lex.get_table_list()->derived) { /* it is single table update and it is update of derived table */ my_error(ER_NON_UPDATABLE_TABLE, MYF(0), lex->select_lex.get_table_list()->alias, "UPDATE"); MYSQL_YYABORT; } /* In case of multi-update setting write lock for all tables may be too pessimistic. We will decrease lock level if possible in mysql_multi_update(). */ Select->set_lock_for_tables($3); } where_clause opt_order_clause delete_limit_clause {} ; update_list: update_list ',' update_elem | update_elem ; update_elem: simple_ident_nospvar equal expr_or_default { if (add_item_to_list(YYTHD, $1) || add_value_to_list(YYTHD, $3)) MYSQL_YYABORT; } ; |
非常清晰的定义!field保存到了lex->select_lex.item_list中,而value保存在lex->value_list中,并且它们是一一对应的
规则是嵌套定义的,如果想了解where条件怎么解析的,可以查看规则where_clause:
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(); } ; |
整个where条件会被规约成一个expr,保存在lex->where中
下面以一个例子说明update解析后的结果:
update t2 set b=b*2-1, c=c/2+4 where id=5;
set左值集合:fields
thd->lex->select_lex.item_list List<Item> |-->Item_field(b) |-->Item_field(c) |
set右值集合:values:
thd->lex->value_list list<Item> |-->Item_func_minus |-->args[0]:Item_func_mul |-->args[0]:Item_field(b) |-->args[1]:Item_int(2) |-->args[1]:Item_int(1) |-->Item_func_plus |-->args[0]:Item_func_div |-->args[0]:Item_field(c) |-->args[1]:Item_int(2) |-->args[1]:Item_int(4) |
where 条件:
thd->lex->select_lex.where Item_func_eq |-->args[0]:Item_field(id) |-->args[1]:Item_int(5) |
Debug Tips
1. 在sql_yacc.yy的文件头部加上一行
%verbose
make之后会生成sql_yacc.output文件,其中有关状态机的详细信息,包括各个状态与定义规则的对应关系,shift/reduce行为,规则冲突信息
state 568 1575 update: UPDATE_SYM $@142 . opt_low_priority opt_ignore join_table_list SET update_list $@143 where_clause opt_order_cla use delete_limit_clause LOW_PRIORITY shift, and go to state 844 $default reduce using rule 1582 (opt_low_priority) opt_low_priority go to state 936 state 936 1575 update: UPDATE_SYM $@142 opt_low_priority . opt_ignore join_table_list SET update_list $@143 where_clause opt_order_clause delete_limit_clause IGNORE_SYM shift, and go to state 120 $default reduce using rule 910 (opt_ignore) opt_ignore go to state 1399 |
注:’.’表示当前状态对应的位置
2. 启动MySQL时,打开parser_debug标志可以看到sql解析过程中的状态转移信息
/u01/mysql/bin/mysqld –defaults-file=/u01/mysql/my.cnf –debug=”d,parser_debug” &
解析 “update t2 set b=b*2-1, c=c/2+4 where id=5” 时产生的调试信息:
Stack now 0 53 568 936 1399 1844 2341 2753 3100 2230 2657 Entering state 765 Reducing stack by rule 1575 (line 10698): $1 = token UPDATE_SYM () $2 = nterm $@142 () $3 = nterm opt_low_priority () $4 = nterm opt_ignore () $5 = nterm join_table_list () $6 = token SET () $7 = nterm update_list () $8 = nterm $@143 () $9 = nterm where_clause () $10 = nterm opt_order_clause () $11 = nterm delete_limit_clause () -> $$ = nterm update () Stack now 0 Entering state 90 Reducing stack by rule 58 (line 1771): $1 = nterm update () -> $$ = nterm statement () Stack now 0 Entering state 59 Reducing stack by rule 7 (line 1716): $1 = nterm statement () -> $$ = nterm verb_clause () Stack now 0 Entering state 58 Next token is token END_OF_INPUT () Shifting token END_OF_INPUT () Entering state 582 Reducing stack by rule 4 (line 1703): $1 = nterm verb_clause () $2 = token END_OF_INPUT () -> $$ = nterm query () Stack now 0 Entering state 57 Reading a token: Now at end of input. Shifting token $end () Entering state 581 Stack now 0 57 581 Cleanup: popping token $end () Cleanup: popping nterm query () |
纠正个笔误 :)
ha_rows *found_return, ha_rows *found_return);
found_return => updated_return
Good!
比如我有一张player 表。。里面有三个字段,id, name, sex
我要更新这个人的性别但这样写了update player set id=1, name=’ss’, sex=0 where id=1
其实改变的只有sex
这样会全部更新么。。