• GreatSQL社区原创内容未经受权不得随便应用,转载请分割小编并注明起源。

[toc]

一、问题发现

在一次开发中应用 MySQL PREPARE 当前,从 prepare 间接取 name 赋值给 lex->prepared_stmt_name 而后给 EXECUTE 用,发现有肯定概率找不到 prepare stmt 的 name,于是开始入手考察问题产生的起因。

SQL语句示例:

CREATE TABLE t1 (a INT, b VARCHAR(10));PREPARE dbms_sql_stmt4 FROM 'INSERT INTO t1 VALUES (1,''11'')';EXECUTE dbms_sql_stmt4;报错:SQL Error [1243] [HY000]: Unknown prepared statement handler (dbms_sql_stmt4??p??]UU) given to EXECUTE

二、问题调查过程

1、依据报错信息找到对应源码,发现在MySQL_sql_stmt_execute外面有判断当找不到 stmt name 时候报错信息。

这里的 name 此时曾经是乱码了。

void MySQL_sql_stmt_execute(THD *thd) {  LEX *lex = thd->lex;  const LEX_CSTRING &name = lex->prepared_stmt_name;  DBUG_TRACE;  DBUG_PRINT("info", ("EXECUTE: %.*s\n", (int)name.length, name.str));  Prepared_statement *stmt;  if (!(stmt = thd->stmt_map.find_by_name(name))) {    my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), static_cast<int>(name.length),             name.str, "EXECUTE");    return;  }

2、这个 lex->prepared_stmt_name 是从 prepare name 中赋值的,于是考察 prepare 这个 name 设置的函数。

bool Prepared_statement::set_name(const LEX_CSTRING &name_arg) {  m_name.length = name_arg.length;  m_name.str = static_cast<char *>(      memdup_root(m_arena.mem_root, name_arg.str, name_arg.length));  return m_name.str == nullptr;}

gdb 跟踪代码:

Thread 46 "MySQLd" hit Breakpoint 1, Prepared_statement::set_name (this=0x7fff2cbf3250, name_arg=...)    at /home/wuyy/greatdb/gitmerge/percona-server/sql/sql_prepare.cc:24472447    bool Prepared_statement::set_name(const LEX_CSTRING &name_arg) {(gdb) n2448      m_name.length = name_arg.length;(gdb) 2450          memdup_root(m_arena.mem_root, name_arg.str, name_arg.length));(gdb) 2449      m_name.str = static_cast<char *>((gdb) 2451      return m_name.str == nullptr;(gdb) p m_name$9 = {  str = 0x7fff2cd09a68 "dbms_sql_stmt4", '\217' <repeats 98 times>, "FLOAT",  length = 14# 能够看到 m_name 前面呈现了乱码,阐明 m_nam e最初不是 \0 完结,而是别的字符。

3、接着到 execute 的函数看一下这个 name 值,发现的确前面跟的不是 \0 结束符,而是变为乱码。于是这里当然会报错找不到该 stmt name 了。

Thread 46 "MySQLd" hit Breakpoint 2, MySQL_sql_stmt_execute (thd=0x7fff2c002688)    at /home/wuyy/greatdb/gitmerge/percona-server/sql/sql_prepare.cc:19441944    void MySQL_sql_stmt_execute(THD *thd) {(gdb) n1945      LEX *lex = thd->lex;(gdb) 1946      const LEX_CSTRING &name = lex->prepared_stmt_name;(gdb) 1947      DBUG_TRACE;(gdb) p name$10 = (const LEX_CSTRING &) @0x7fff2cd501e0: {  str = 0x7fff2cd09a68 "dbms_sql_stmt4\217\217p\271\221]UU",  length = 22}(gdb) n1948      DBUG_PRINT("info", ("EXECUTE: %.*s\n", (int)name.length, name.str));(gdb) 1951      if (!(stmt = thd->stmt_map.find_by_name(name))) {(gdb) 1953                 name.str, "EXECUTE");(gdb) 1952        my_error(ER_UNKNOWN_STMT_HANDLER, MYF(0), static_cast<int>(name.length),(gdb) 1954        return;# 后果报错了。

三、问题解决方案

通过以上 gdb 跟踪过程咱们能够发现 prepare 存 name 的时候寄存形式有问题导致 name 最初没有结束符,于是回头看一下set_name 的代码,于是发现以下代码问题:

bool Prepared_statement::set_name(const LEX_CSTRING &name_arg) {  m_name.length = name_arg.length;  m_name.str = static_cast<char *>(      memdup_root(m_arena.mem_root, name_arg.str, name_arg.length));←这里问题  return m_name.str == nullptr;}# 箭头处发现存 name 时候申请的内存长度为 name_arg.length,没有把最初的 \0 一起寄存进去,导致最初少了结束符,这就有概率导致查找 name 出错。

于是把 name_arg.length 改为 name_arg.length+1,从新编译代码问题解决。

四、问题总结

c++ 中字符串的应用肯定要留神最初的结束符\0,如果因为少调配了一个长度导致结束符没有存进去,最初寄存的字符串就会产生问题。

Enjoy GreatSQL :)

本文由博客一文多发平台 OpenWrite 公布!