MySQL的prepare使用及遇到bug解析過程
一、問題發(fā)現
在一次開發(fā)中使用 MySQL PREPARE 以后,從 prepare 直接取 name 賦值給 lex->prepared_stmt_name 然后給 EXECUTE 用,發(fā)現有一定概率找不到 prepare stmt 的 name,于是開始動手調查問題發(fā)生的原因。
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、根據報錯信息找到對應源碼,發(fā)現在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: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
值,發(fā)現確實后面跟的不是 \0
結束符,而是變?yōu)閬y碼。于是這里當然會報錯找不到該 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<int>(name.length), (gdb) 1954 return; # 結果報錯了。
三、問題解決方案
通過以上 gdb 跟蹤過程我們可以發(fā)現 prepare 存 name 的時候存放方式有問題導致 name 最后沒有結束符,于是回頭看一下set_name 的代碼,于是發(fā)現以下代碼問題:
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; } # 箭頭處發(fā)現存 name 時候申請的內存長度為 name_arg.length,沒有把最后的 \0 一起存放進去,導致最后少了結束符,這就有概率導致查找 name 出錯。
于是把 name_arg.length 改為 name_arg.length+1,重新編譯代碼問題解決。
四、問題總結
c++ 中字符串的使用一定要注意最后的結束符\0
,如果因為少分配了一個長度導致結束符沒有存進去,最后存放的字符串就會產生問題。
到此這篇關于MySQL的prepare使用及遇到bug解析過程的文章就介紹到這了,更多相關mysql prepare使用內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SQL中distinct 和 row_number() over() 的區(qū)別及用法
這篇文章主要介紹了SQL中distinct 和 row_number() over() 的區(qū)別及用法的相關資料,需要的朋友可以參考下2017-03-03實例驗證MySQL|update字段為相同的值是否會記錄binlog
這篇文章主要介紹了實例驗證MySQL|update字段為相同的值是否會記錄binlog,幫助大家更好的理解和學習MySQL數據庫,感興趣的朋友可以了解下2020-10-10