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 相关文章推荐
仅用一句SQL更新整张表的涨跌幅、涨跌率的解决方案
May 06 MySQL
Mysql基础知识点汇总
May 26 MySQL
Mysql 如何查询时间段交集
Jun 08 MySQL
新手入门Mysql--sql执行过程
Jun 20 MySQL
分析mysql中一条SQL查询语句是如何执行的
Jun 21 MySQL
MySQL系列之开篇 MySQL关系型数据库基础概念
Jul 02 MySQL
QT连接MYSQL数据库的详细步骤
Jul 07 MySQL
Centos7中MySQL数据库使用mysqldump进行每日自动备份的编写
Aug 02 MySQL
Mysql中where与on的区别及何时使用详析
Aug 04 MySQL
MySQL约束超详解
Sep 04 MySQL
MySQL 表锁定 LOCK和UNLOCK TABLES的 SQL语法
Apr 18 MySQL
mysql字段为NULL索引是否会失效实例详解
May 30 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
模板引擎Smarty深入浅出介绍
2006/12/06 PHP
PHP foreach循环使用详解与实例代码
2010/05/08 PHP
PHP中的float类型使用说明
2010/07/27 PHP
PHP使用get_headers函数判断远程文件是否存在的方法
2014/11/28 PHP
在你的网页中嵌入外部网页的方法
2007/04/02 Javascript
jquery $.ajax()取xml数据的小问题解决方法
2010/11/20 Javascript
jqueryUI里拖拽排序示例分析
2015/02/26 Javascript
D3.js中data(), enter() 和 exit()的问题详解
2015/08/17 Javascript
基于canvas实现的绚丽圆圈效果完整实例
2016/01/26 Javascript
基于JS实现textarea中获取动态剩余字数的方法
2016/05/25 Javascript
全面解析jQuery $(document).ready()和JavaScript onload事件
2016/06/08 Javascript
angular分页指令操作
2017/01/09 Javascript
Angular.js中定时器循环的3种方法总结
2017/04/27 Javascript
Vue自定义指令使用方法详解
2017/08/21 Javascript
Vue.js实现表格渲染的方法
2018/09/07 Javascript
使用 UniApp 实现小程序的微信登录功能
2020/06/09 Javascript
在Python中使用元类的教程
2015/04/28 Python
Python3指定路径寻找符合匹配模式文件
2015/05/22 Python
利用python实现对web服务器的目录探测的方法
2019/02/26 Python
python的turtle库使用详解
2019/05/10 Python
Python基于WordCloud制作词云图
2019/11/29 Python
简述python Scrapy框架
2020/08/17 Python
Python爬虫实现自动登录、签到功能的代码
2020/08/20 Python
Python爬虫爬取微博热搜保存为 Markdown 文件的源码
2021/02/22 Python
使用CSS3的::selection改变选中文本颜色的方法
2015/09/29 HTML / CSS
德国童装购物网站:NICKI´S.com
2018/04/20 全球购物
计算机开发个人求职信范文
2013/09/26 职场文书
迟到检讨书5000字
2014/01/31 职场文书
拔河比赛口号
2014/06/10 职场文书
党员教师群众路线个人整改措施
2014/10/28 职场文书
资料员岗位职责范本
2015/04/13 职场文书
班级联欢会主持词
2015/07/03 职场文书
Python3.8官网文档之类的基础语法阅读
2021/09/04 Python
nginx安装以及配置的详细过程记录
2021/09/15 Servers
mysql主从复制的实现步骤
2021/10/24 MySQL
Pygame Event事件模块的详细示例
2021/11/17 Python