注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

从一条错误提示看mysql错误码处理  

2015-08-25 22:04:37|  分类: 数据库 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
基于mysql5.5.28版本
一、子查询单值和集合的不同
从前一直对于mysql的选择条件中既可以支持 someval=(select row from table where somecondition)这类的单值相等查询,也可以支持类似 somecol in (select row from table where somecondition)这样的集合查询而感到好奇,这两个操作的子查询明显时两种不同的要求,一个是要求有一个特定的值,而另一个则是要求有特定的集合,这两个从概念上看差别时如此之大,以至于它们在实现上必定是通过不同的结构来实现的,下面是一个对该问题进行阐述的例子,由于主查询指定的相等子查询返回了多个结果,所以这个查询是失败的。这里看到的现象是,这个查询是否成功时在运行时判定的:
mysql> create database equalsubselect;
Query OK, 1 row affected (0.00 sec)
mysql> use equalsubselect;
Database changed
mysql> create table tsecer (c1 int, c2 int);
Query OK, 0 rows affected (0.19 sec)
mysql> insert tsecer values (1,1) , (1,2);
Query OK, 2 rows affected (0.06 sec)
Records: 2  Duplicates: 0  Warnings: 0
mysql> select * from tsecer;
+------+------+
| c1   | c2   |
+------+------+
|    1 |    1 |
|    1 |    2 |
+------+------+
2 rows in set (0.06 sec)
mysql> select * from tsecer where (select c1 from tsecer where c2 < 10);
ERROR 1242 (21000): Subquery returns more than 1 row
二、mysql错误的生成
在mysql的源代码结构中,所有的错误码及描述定义在文件mysql-5.5.28\sql\share\errmsg-utf8.txt,这是一个简单的文本文件,符合unix配置文件的格式。这个文件对于人工维护来说相对比较友好,但是对于计算机处理有些吃力,所以需要在编译的时候对这个文件进行“预处理”,转换为对计算机友好的文件格式,这个转换过程在工程文件mysql-5.5.28\extra\comp_err.c中完成,这是一个简单的特定文本文件处理工具,处理结果包括了mysqld_error.h、mysqld_ername.h及二进制格式的、每种语言特有的errmsg.sys,这些文件的名字在derror.c中均有体现。该文件main函数的主题包括了
parse_input_file
create_header_files(包括了static char *HEADERFILE= (char*) "mysqld_error.h"、static char *NAMEFILE= (char*) "mysqld_ername.h"、static char *STATEFILE= (char*) "sql_state.h"三个主要的头文件)
create_sys_files
在parse_input_file
    if (*str == '\t' || *str == ' ')
    {
      /* New error message in another language for previous error */
……
    }

    if (is_prefix(str, ER_PREFIX) || is_prefix(str, WARN_PREFIX))//其中#define ER_PREFIX "ER_" #define WARN_PREFIX "WARN_"
    {
……
      rcount++;                         /* Count number of unique errors */

      /* add error to the list */
      *tail_error= current_error;
      tail_error= &current_error->next_error;
      continue;
    }

生成的mysqld_error.h文件内容为下面所示,其中包含了选择结果非单一错误ER_SUBQUERY_NO_1_ROW
/* Autogenerated file, please don't edit */

#define ER_ERROR_FIRST 1000
#define ER_HASHCHK 1000
#define ER_NISAMCHK 1001
……
#define ER_KEY_REF_DO_NOT_MATCH_TABLE_REF 1240
#define ER_OPERAND_COLUMNS 1241
#define ER_SUBQUERY_NO_1_ROW 1242
#define ER_UNKNOWN_STMT_HANDLER 1243
#define ER_CORRUPT_HELP_DB 1244

mysqld_ername.h
/* Autogenerated file, please don't edit */

{ "ER_HASHCHK", 1000, "hashchk" },
{ "ER_NISAMCHK", 1001, "isamchk" },
{ "ER_NO", 1002, "NO" },
{ "ER_YES", 1003, "YES" },
……
{ "ER_OPERAND_COLUMNS", 1241, "Operand should contain %d column(s)" },
{ "ER_SUBQUERY_NO_1_ROW", 1242, "Subquery returns more than 1 row" },
{ "ER_UNKNOWN_STMT_HANDLER", 1243, "Unknown prepared statement handler (%.*s) given to %s" },

sql_state.h文件
/* Autogenerated file, please don't edit */

{ ER_DUP_KEY                              ,"23000", "" },
{ ER_OUTOFMEMORY                          ,"HY001", "S1001" },
……
{ ER_OPERAND_COLUMNS                      ,"21000", "" },
{ ER_SUBQUERY_NO_1_ROW                    ,"21000", "" },
{ ER_ILLEGAL_REFERENCE                    ,"42S22", "" },
……
不过mysqld运行时并没有使用其中的mysqld_ername.h文件,而是使用了二进制文件errmsg.sys中的信息。
三、errmsg.sys的生成及加载
二进制格式的错误描述在create_sys_files中生成,由于错误字符串的长度是变化的,所以函数中首先在文件中预留出32个字节的头结构和 rowcount*2个字节的索引指针结构,然后再接下来的文件空间中写入变成的错误描述字符串,同时记录每个row结构的其实位置,当整个文件写完之后,回过头来在文件开始填充这些信息,包括了每个错误row的文件内其实位置指针及校验和。整个错误row的起始位置是相对于 32字节文件头+ rowcount*2字节的索引头开始的。
mysqld在启动过程中mysql-5.5.28\sql\derror.cc:init_errmessage-->>read_texts
  for (i=1 ; i < textcount ; i++)
  {
    point[i]= *point +uint2korr(head+10+i+i);
  }
读取之后通过my_error_register注册给mysqld进程,在打印错误时,mysql-5.5.28\mysys\my_error.c:my_error函数通过meh_p->get_errmsgs()[nr - meh_p->meh_first]来获得错误码字符串的描述,由于错误码减去起始错误码作为数组下标(mysql-5.5.28\sql\share\errmsg-utf8.txt:start-error-number 1000),因此访问速度非常快。

  /* Search for the error messages array, which could contain the message. */
  for (meh_p= my_errmsgs_list; meh_p; meh_p= meh_p->meh_next)
    if (nr <= meh_p->meh_last)
      break;

  /* get the error message string. Default, if NULL or empty string (""). */
  if (! (format= (meh_p && (nr >= meh_p->meh_first)) ?
                  meh_p->get_errmsgs()[nr - meh_p->meh_first] : NULL) || ! *format)
    (void) my_snprintf (ebuff, sizeof(ebuff), "Unknown error %d", nr);
  else
  {
    va_start(args,MyFlags);
    (void) my_vsnprintf_ex(&my_charset_utf8_general_ci, ebuff,
                           sizeof(ebuff), format, args);
    va_end(args);
  }

四、回到查询错误中
在mysql-5.5.28\sql\share\errmsg-utf8.txt中查询错误提示Subquery returns more than 1 row,可以找到下面内容,然后在项目中搜索ER_SUBQUERY_NO_1_ROW:

ER_SUBQUERY_NO_1_ROW 21000 
        eng "Subquery returns more than 1 row"

幸运的是,明显的使用这个错误码的地方只有一个mysql-5.5.28\sql\sql_class.cc
bool select_singlerow_subselect::send_data(List<Item> &items)
{
  DBUG_ENTER("select_singlerow_subselect::send_data");
  Item_singlerow_subselect *it= (Item_singlerow_subselect *)item;
  if (it->assigned())
  {
    my_message(ER_SUBQUERY_NO_1_ROW, ER(ER_SUBQUERY_NO_1_ROW), MYF(0));
    DBUG_RETURN(1);
  }

由于错误码位置如此之明显,设置一个断点,然后再执行下查询命令,即可以看到调用链:

子查询返回第一个结果时的调用链(此时还没有报错):
> mysqld.exe!Item_singlerow_subselect::store(unsigned int i=0, Item * item=0x0ab41a78)  行499 C++
  mysqld.exe!select_singlerow_subselect::send_data(List<Item> & items={...})  行2747 + 0x10 字节 C++
  mysqld.exe!end_send(JOIN * join=0x0ab48598, st_join_table * join_tab=0x0ab42998, bool end_of_records=false)  行12680 + 0x27 字节 C++
  mysqld.exe!evaluate_join_record(JOIN * join=0x0ab48598, st_join_table * join_tab=0x0ab427f8, int error=0)  行11892 + 0x1a 字节 C++
  mysqld.exe!sub_select(JOIN * join=0x0ab48598, st_join_table * join_tab=0x0ab427f8, bool end_of_records=false)  行11745 + 0x11 字节 C++
  mysqld.exe!do_select(JOIN * join=0x0ab48598, List<Item> * fields=0x0ab4152c, TABLE * table=0x00000000, Procedure * procedure=0x00000000)  行11510 + 0xf 字节 C++
  mysqld.exe!JOIN::exec()  行2379 + 0x19 字节 C++
  mysqld.exe!subselect_single_select_engine::exec()  行2010 C++
  mysqld.exe!Item_subselect::exec()  行277 + 0x15 字节 C++
  mysqld.exe!Item_singlerow_subselect::val_int()  行591 + 0x12 字节 C++
  mysqld.exe!eval_const_cond(Item * cond=0x0ab42118)  行79 + 0xf 字节 C++
  mysqld.exe!internal_remove_eq_conds(THD * thd=0x0aac8f68, Item * cond=0x0ab42118, Item::cond_result * cond_value=0x0ab579a8)  行9591 + 0x9 字节 C++
  mysqld.exe!remove_eq_conds(THD * thd=0x0aac8f68, Item * cond=0x0ab42118, Item::cond_result * cond_value=0x0ab579a8)  行9684 + 0x11 字节 C++
  mysqld.exe!optimize_cond(JOIN * join=0x0ab56888, Item * conds=0x0ab42118, List<TABLE_LIST> * join_list=0x0aaca484, Item::cond_result * cond_value=0x0ab579a8)  行9449 + 0x11 字节 C++
  mysqld.exe!JOIN::optimize()  行924 + 0x26 字节 C++
  mysqld.exe!mysql_select(THD * thd=0x0aac8f68, Item * * * rref_pointer_array=0x0aaca4b8, TABLE_LIST * tables=0x0ab410d0, unsigned int wild_num=1, List<Item> & fields={...}, Item * conds=0x0ab42118, unsigned int og_num=0, st_order * order=0x00000000, st_order * group=0x00000000, Item * having=0x00000000, st_order * proc_param=0x00000000, unsigned __int64 select_options=2147748608, select_result * result=0x0ab42240, st_select_lex_unit * unit=0x0aac9f70, st_select_lex * select_lex=0x0aaca3b8)  行2574 + 0x8 字节 C++
  mysqld.exe!handle_select(THD * thd=0x0aac8f68, LEX * lex=0x0aac9f08, select_result * result=0x0ab42240, unsigned long setup_tables_done_option=0)  行297 + 0x9e 字节 C++
  mysqld.exe!execute_sqlcom_select(THD * thd=0x0aac8f68, TABLE_LIST * all_tables=0x0ab410d0)  行4590 + 0x13 字节 C++
  mysqld.exe!mysql_execute_command(THD * thd=0x0aac8f68)  行2151 + 0xd 字节 C++
  mysqld.exe!mysql_parse(THD * thd=0x0aac8f68, char * rawbuf=0x0ab40f10, unsigned int length=64, Parser_state * parser_state=0x0cf7fcb4)  行5627 + 0x9 字节 C++
  mysqld.exe!dispatch_command(enum_server_command command=COM_QUERY, THD * thd=0x0aac8f68, char * packet=0x0ab38c21, unsigned int packet_length=64)  行1037 + 0x22 字节 C++
  mysqld.exe!do_command(THD * thd=0x0aac8f68)  行773 + 0x1b 字节 C++
  mysqld.exe!do_handle_one_connection(THD * thd_arg=0x0aac8f68)  行840 + 0x9 字节 C++
  mysqld.exe!handle_one_connection(void * arg=0x0aac8f68)  行759 + 0x9 字节 C++
  mysqld.exe!pthread_start(void * p=0x0aadfcf8)  行61 + 0x9 字节 C
  mysqld.exe!_callthreadstartex()  行348 + 0xf 字节 C
  mysqld.exe!_threadstartex(void * ptd=0x0ab3cc68)  行331 C

出错时调用链
> mysqld.exe!select_singlerow_subselect::send_data(List<Item> & items={...})  行2736 C++
  mysqld.exe!end_send(JOIN * join=0x0ab48598, st_join_table * join_tab=0x0ab42998, bool end_of_records=false)  行12680 + 0x27 字节 C++
  mysqld.exe!evaluate_join_record(JOIN * join=0x0ab48598, st_join_table * join_tab=0x0ab427f8, int error=0)  行11892 + 0x1a 字节 C++
  mysqld.exe!sub_select(JOIN * join=0x0ab48598, st_join_table * join_tab=0x0ab427f8, bool end_of_records=false)  行11750 + 0x11 字节 C++
  mysqld.exe!do_select(JOIN * join=0x0ab48598, List<Item> * fields=0x0ab4152c, TABLE * table=0x00000000, Procedure * procedure=0x00000000)  行11510 + 0xf 字节 C++
  mysqld.exe!JOIN::exec()  行2379 + 0x19 字节 C++
  mysqld.exe!subselect_single_select_engine::exec()  行2010 C++
  mysqld.exe!Item_subselect::exec()  行277 + 0x15 字节 C++
  mysqld.exe!Item_singlerow_subselect::val_int()  行591 + 0x12 字节 C++
  mysqld.exe!eval_const_cond(Item * cond=0x0ab42118)  行79 + 0xf 字节 C++
  mysqld.exe!internal_remove_eq_conds(THD * thd=0x0aac8f68, Item * cond=0x0ab42118, Item::cond_result * cond_value=0x0ab579a8)  行9591 + 0x9 字节 C++
  mysqld.exe!remove_eq_conds(THD * thd=0x0aac8f68, Item * cond=0x0ab42118, Item::cond_result * cond_value=0x0ab579a8)  行9684 + 0x11 字节 C++
  mysqld.exe!optimize_cond(JOIN * join=0x0ab56888, Item * conds=0x0ab42118, List<TABLE_LIST> * join_list=0x0aaca484, Item::cond_result * cond_value=0x0ab579a8)  行9449 + 0x11 字节 C++
  mysqld.exe!JOIN::optimize()  行924 + 0x26 字节 C++
  mysqld.exe!mysql_select(THD * thd=0x0aac8f68, Item * * * rref_pointer_array=0x0aaca4b8, TABLE_LIST * tables=0x0ab410d0, unsigned int wild_num=1, List<Item> & fields={...}, Item * conds=0x0ab42118, unsigned int og_num=0, st_order * order=0x00000000, st_order * group=0x00000000, Item * having=0x00000000, st_order * proc_param=0x00000000, unsigned __int64 select_options=2147748608, select_result * result=0x0ab42240, st_select_lex_unit * unit=0x0aac9f70, st_select_lex * select_lex=0x0aaca3b8)  行2574 + 0x8 字节 C++
  mysqld.exe!handle_select(THD * thd=0x0aac8f68, LEX * lex=0x0aac9f08, select_result * result=0x0ab42240, unsigned long setup_tables_done_option=0)  行297 + 0x9e 字节 C++
  mysqld.exe!execute_sqlcom_select(THD * thd=0x0aac8f68, TABLE_LIST * all_tables=0x0ab410d0)  行4590 + 0x13 字节 C++
  mysqld.exe!mysql_execute_command(THD * thd=0x0aac8f68)  行2151 + 0xd 字节 C++
  mysqld.exe!mysql_parse(THD * thd=0x0aac8f68, char * rawbuf=0x0ab40f10, unsigned int length=64, Parser_state * parser_state=0x0cf7fcb4)  行5627 + 0x9 字节 C++
  mysqld.exe!dispatch_command(enum_server_command command=COM_QUERY, THD * thd=0x0aac8f68, char * packet=0x0ab38c21, unsigned int packet_length=64)  行1037 + 0x22 字节 C++
  mysqld.exe!do_command(THD * thd=0x0aac8f68)  行773 + 0x1b 字节 C++
  mysqld.exe!do_handle_one_connection(THD * thd_arg=0x0aac8f68)  行840 + 0x9 字节 C++
  mysqld.exe!handle_one_connection(void * arg=0x0aac8f68)  行759 + 0x9 字节 C++
  mysqld.exe!pthread_start(void * p=0x0aadfcf8)  行61 + 0x9 字节 C
  mysqld.exe!_callthreadstartex()  行348 + 0xf 字节 C
  mysqld.exe!_threadstartex(void * ptd=0x0ab3cc68)  行331 C

也就是在sub_select函数的这个循环中进行第二次赋值时报错字段有重复
  while (rc == NESTED_LOOP_OK)
  {
    error= info->read_record(info);
    rc= evaluate_join_record(join, join_tab, error);
  }

五、从语法文件角度看单值查询和集合查询
语法文件定义在mysql-5.5.28\sql\sql_yacc.yy中:
对于集合查询,它创建的表示结构为一个Item_in_subselect对象,它的语法单位为predicate,这个语法单位的优先级要比单值查询的优先级低。
predicate:
          bit_expr IN_SYM '(' subselect ')'
          {
            $$= new (YYTHD->mem_root) Item_in_subselect($1, $4);
            if ($$ == NULL)
              MYSQL_YYABORT;
          }
……
对于单值查询,它的语法结构为simple_expr,对应的表示对象为Item_singlerow_subselect,不同的类有不同的处理方法。从而语法结构的不同导致了表示对象的不同,进而让终端用户看到行为的不同。
simple_expr:
          simple_ident
        | function_call_keyword
……
        | '(' subselect ')'
          { 
            $$= new (YYTHD->mem_root) Item_singlerow_subselect($2);
            if ($$ == NULL)
              MYSQL_YYABORT;
          }

  评论这张
 
阅读(193)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017