MySQL的prepare使用及遇到bug解析过程

吾爱主题 阅读:184 2024-04-01 23:25:20 评论:0

一、问题发现

在一次开发中使用 MySQL PREPARE 以后,从 prepare 直接取 name 赋值给 lex->prepared_stmt_name 然后给 EXECUTE 用,发现有一定概率找不到 prepare stmt 的 name,于是开始动手调查问题发生的原因。

SQL语句示例:

?
1 2 3 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 此时已经是乱码了。

?
1 2 3 4 5 6 7 8 9 10 11 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 设置的函数。

?
1 2 3 4 5 6 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 跟踪代码:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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< 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 了。

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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< int >( name .length), (gdb) 1954        return ; # 结果报错了。

三、问题解决方案

通过以上 gdb 跟踪过程我们可以发现 prepare 存 name 的时候存放方式有问题导致 name 最后没有结束符,于是回头看一下set_name 的代码,于是发现以下代码问题:

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

到此这篇关于MySQL的prepare使用及遇到bug解析过程的文章就介绍到这了,更多相关mysql prepare使用内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://juejin.cn/post/7094812134831292446

可以去百度分享获取分享代码输入这里。
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

【腾讯云】云服务器产品特惠热卖中
搜索
标签列表
    关注我们

    了解等多精彩内容