黄东旭解析 TiDB 的核心优势
559
2023-04-24
MySQL 的prepare使用中的bug解析过程
一、问题发现 二、问题调查过程 三、问题解决方案 四、问题总结
一、问题发现
在一次开发中使用 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
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
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
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
三、问题解决方案
通过以上 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
于是把 name_arg.length 改为 name_arg.length+1,重新编译代码问题解决。
四、问题总结
c++ 中字符串的使用一定要注意最后的结束符\0,如果因为少分配了一个长度导致结束符没有存进去,最后存放的字符串就会产生问题。
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。