jlzzjlzz亚洲乱熟在线播放

系統城裝機大師 - 唯一官網:www.farandoo.com!

當前位置:首頁 > 數據庫 > Mysql > 詳細頁面

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

時間:2022-05-08來源:www.farandoo.com作者:電腦系統城

一、問題發現

在一次開發中使用 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解析過程的文章就介紹到這了

分享到:

相關信息

系統教程欄目

欄目熱門教程

人氣教程排行

站長推薦

熱門系統下載