1.问题发现2.问题排查过程3.问题解决4.问题总结1.问题发现一个开发中使用MySQLPREPARE后,直接从prepare中取名并赋值ittolex->prepared_stmt_name然后用于EXECUTE,发现有一定概率找不到preparestmt的名字,于是开始排查问题原因。SQL语句示例:CREATETABLEt1(aINT,bVARCHAR(10));PREPAREdbms_sql_stmt4FROM'INSERTINTOt1VALUES(1,''11'')';执行dbms_sql_stmt4;Error:SQLError[1243][HY000]:Unknownpreparedstatementhandler(dbms_sql_stmt4??p??]UU)giventoEXECUTE二、问题排查过程1、根据错误信息找到对应的源码,发现有是mysql_sql_stmt_execute中的一个判断。当找不到stmt名称时,会报错。这里的名字此时已经是乱码了。voidMySQL_sql_stmt_execute(THD*thd){LEX*lex=thd->lex;constLEX_CSTRING&name=lex->prepared_stmt_name;DBUG_TRACE;DBUG_PRINT("info",("EXECUTE:%.*s\n",(int)name.length,name.str));Prepared_statement*stmt;如果(!(stmt=thd->stmt_map.find_by_name(name))){my_error(ER_UNKNOWN_STMT_HANDLER,MYF(0),static_cast(name.length),name.str,"EXECUTE");返回;}2。lex->prepared_stmt_name是从prepare名称分配的,所以调查由名称prepare设置的函数。boolPrepared_statement::set_name(constLEX_CSTRING&name_arg){m_name.length=name_arg.length;m_name.str=static_cast(memdup_root(m_arena.mem_root,name_arg.str,name_arg.length));returnm_name.str==nullptr;}gdbtracecode:Thread46"MySQLd"hitBreakpoint1,Prepared_statement::set_name(this=0x7fff2cbf3250,name_arg=...)在/home/wuyy/greatdb/gitmerge/percona-server/sql/sql_prepare.cc:24472447boolPrepared_statement::set_name(constLEX_CSTRING&name_arg){(gdb)n2448m_name.length=name_arg.length;(gdb)2450memdup_root(m_arena.mem_root,name_arg.str,name_arg.length));(gdb)2449m_name.str=static_cast((gdb)2451returnm_name.str==nullptr;(gdb)pm_name$9={str=0x7fff2cd09a68"dbms_sql_stmt4",'\217'<重复98次>,"FLOAT",length=14#可以看到m_name后面有乱码,说明m_namee以\0结尾而不是其他字符3.然后到execute函数中看name值,发现确实是后面没有跟\0而不是终止符,就变成了乱码。所以这里当然会报错,找不到stmt名称。线程46“MySQLd”命中断点2,MySQL_sql_stmt_execute(thd=0x7fff2c002688)at/home/wuyy/greatdb/gitmerge/percona-server/sql/sql_prepare.cc:19441944voidMySQL_sql_stmt_execute(THD*thd){(gdb)n1945LEX*lex=thd->lex;(gdb)1946常量LEX_CSTRING&name=lex->prepared_stmt_name;(gdb)1947DBUG_TRACE;(gdb)pname$10=(constLEX_CSTRING&)@0x7fff2cd501e0:{str=0x7fff2cd09a68"dbms_sql_stmt4\217217p\271\221]UU",length=22}(gdb)n1948DBUG_PRINT("info",("EXECUTE:%.*s\n",(int)name.length,name.str));(gdb)1951if(!(stmt=thd->stmt_map.find_by_name(name))){(gdb)1953name.str,"EXECUTE");(gdb)1952my_error(ER_UNKNOWN_STMT_HANDLER,MYF(0),static_cast(name.length),(gdb)1954return;#结果报错了。3.问题解决通过上面的gdb跟踪过程,我们可以发现prepare保存name的时候存储方式有问题,所以name最后没有结束符,所以我们回头看一下set_name的代码,并找到以下代码问题:boolPrepared_statement::set_name(constLEX_CSTRING&name_arg){m_name.length=name_arg.length;m_name.str=static_cast(memdup_root(m_arena.mem_root,name_arg.str,name_arg.length));←这里是问题returnm_name.str==nullptr;}#箭头处发现保存name时请求的内存长度为name_arg.length,最后的\0没有一起存储,导致缺少末尾的终止符,这可能会导致查找名称时出错。于是将name_arg.length改为name_arg.length+1,重新编译代码,问题解决。4.问题总结在c++中使用字符串时,一定要注意最后的终止符\0。如果由于缺少长度而未存储终止符,则最后存储的字符串将导致问题。