MySQL的prepare使用以及遇到的bug


Posted in MySQL onMay 11, 2022

一、问题发现

在一次开发中使用 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<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 值,发现确实后面跟的不是 \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<int>(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<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 相关文章推荐
MySQL之DML语言
Apr 05 MySQL
Mysql 性能监控及调优
Apr 06 MySQL
MySQL 表空间碎片的概念及相关问题解决
May 07 MySQL
mysql中int(3)和int(10)的数值范围是否相同
Oct 16 MySQL
mysql下的max_allowed_packet参数设置详解
Feb 12 MySQL
MySQL优化常用的19种有效方法(推荐!)
Mar 17 MySQL
MySQL实现配置主从复制项目实践
Mar 31 MySQL
MySQL时区造成时差问题
Apr 13 MySQL
MySQL 字符集 character
May 04 MySQL
MySQL 自动填充 create_time 和 update_time
May 20 MySQL
MySQL主从切换的超详细步骤
Jun 28 MySQL
MySQL的意向共享锁、意向排它锁和死锁
Jul 15 MySQL
MySQL批量更新不同表中的数据
May 11 #MySQL
mysql查找连续出现n次以上的数字
May 11 #MySQL
mysql如何查询连续记录
May 11 #MySQL
mysql 体系结构和存储引擎介绍
MySQL数据库 安全管理
May 06 #MySQL
Mysql 文件配置解析介绍
May 06 #MySQL
MySQL数据库中的锁、解锁以及删除事务
May 06 #MySQL
You might like
PHP header()函数使用详细(301、404等错误设置)
2013/04/17 PHP
WebQQ最新登陆协议的用法
2014/12/22 PHP
PHP实现的分页类定义与用法示例
2017/07/05 PHP
使用Git实现Laravel项目的自动化部署
2019/11/24 PHP
用javascript实现自定义标签
2007/05/08 Javascript
Ajax,UTF-8还是GB2312 eval 还是execScript
2008/11/13 Javascript
jquery maxlength使用说明
2011/09/09 Javascript
js内存泄露的几种情况详细探讨
2013/05/31 Javascript
nodejs中操作mysql数据库示例
2014/12/20 NodeJs
浅析jQuery Mobile的初始化事件
2015/12/03 Javascript
Boostrap入门准备之border box
2016/05/09 Javascript
JavaScript函数柯里化原理与用法分析
2017/03/31 Javascript
javascript trie前缀树的示例
2018/01/29 Javascript
微信小程序登录换取token的教程
2018/05/31 Javascript
vue中解决chrome浏览器自动播放音频和MP3语音打包到线上的实现方法
2020/10/09 Javascript
利用PHP实现递归删除链表元素的方法示例
2020/10/23 Javascript
[05:59]2018DOTA2国际邀请赛寻真——只为胜利的Secret
2018/08/13 DOTA
让python同时兼容python2和python3的8个技巧分享
2014/07/11 Python
python入门教程之识别验证码
2017/03/04 Python
浅谈python socket函数中,send与sendall的区别与使用方法
2017/05/09 Python
CentOS 6.5中安装Python 3.6.2的方法步骤
2017/12/03 Python
Python实现的微信好友数据分析功能示例
2018/06/21 Python
python3 爬取图片的实例代码
2018/11/06 Python
python定位xpath 节点位置的方法
2019/08/27 Python
python3中利用filter函数输出小于某个数的所有回文数实例
2019/11/24 Python
Softmax函数原理及Python实现过程解析
2020/05/22 Python
浅谈three.js中的needsUpdate的应用
2012/11/12 HTML / CSS
运动会广播稿200字
2014/01/15 职场文书
绘画专业自荐信范文
2014/02/23 职场文书
财务管理职业生涯规划书
2014/02/26 职场文书
园林设计专业毕业生求职信
2014/03/23 职场文书
倡导文明标语
2014/06/16 职场文书
交警正风肃纪剖析材料
2014/10/29 职场文书
员工工作能力评语
2014/12/31 职场文书
2014年个人总结范文
2015/03/09 职场文书
《1942》观后感
2015/06/08 职场文书