MySQL 的prepare使用中的bug解析过程

发布时间:2025-05-17 22:10:50 作者:益华网络 来源:undefined 浏览量(1) 点赞(1)
摘要:一、问题发现二、问题调查过程 三、问题解决方案 四、问题总结 一、问题发现 在一次开发中使用 MySQL PREPARE 以后,从 prepare 直接取 name 赋值给 lex->prepared_stmt_name 然后给 EXECUTE 用,

   

 一、问题发现 二、问题调查过程

 三、问题解决方案

 四、问题总结

一、问题发现

在一次开发中使用 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(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(

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:2447

2447 bool Prepared_statement::set_name(const LEX_CSTRING &name_arg) {

(gdb) n

2448 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(

(gdb)

2451 return m_name.str == nullptr;

(gdb) p m_name

$9 = {

str = 0x7fff2cd09a68 "dbms_sql_stmt4", \217 , "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:1944

1944 void MySQL_sql_stmt_execute(THD *thd) {

(gdb) n

1945 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) n

1948 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(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(

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,如果因为少分配了一个长度导致结束符没有存进去,最后存放的字符串就会产生问题。

二维码

扫一扫,关注我们

声明:本文由【益华网络】编辑上传发布,转载此文章须经作者同意,并请附上出处【益华网络】及本页链接。如内容、图片有任何版权问题,请联系我们进行处理。

感兴趣吗?

欢迎联系我们,我们愿意为您解答任何有关网站疑难问题!

您身边的【网站建设专家】

搜索千万次不如咨询1次

主营项目:网站建设,手机网站,响应式网站,SEO优化,小程序开发,公众号系统,软件开发等

立即咨询 15368564009
在线客服
嘿,我来帮您!