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基础(二)
Apr 05 MySQL
MySQL大小写敏感的注意事项
May 24 MySQL
MySQL 不等于的三种使用及区别
Jun 03 MySQL
MySQL 百万级数据的4种查询优化方式
Jun 07 MySQL
mysql备份策略的实现(全量备份+增量备份)
Jul 07 MySQL
MySQL外键约束(FOREIGN KEY)案例讲解
Aug 23 MySQL
Mysql案例刨析事务隔离级别
Sep 25 MySQL
mysql函数全面总结
Nov 11 MySQL
MySQL表类型 存储引擎 的选择
Nov 11 MySQL
mysql 联合索引生效的条件及索引失效的条件
Nov 20 MySQL
mysql 排序失效
May 20 MySQL
mysql拆分字符串作为查询条件的示例代码
Jul 07 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+FLASH实现上传文件进度条相关文件 下载
2007/07/21 PHP
php中文字符截取防乱码
2008/03/28 PHP
Yii中表单用法实例详解
2016/01/05 PHP
对比分析php中Cookie与Session的异同
2016/02/19 PHP
利用phpexcel对数据库数据的导入excel(excel筛选)、导出excel
2017/04/27 PHP
jquery ui 1.7 ui.tabs 动态添加与关闭(按钮关闭+双击关闭)
2010/04/01 Javascript
input:checkbox多选框实现单选效果跟radio一样
2014/06/16 Javascript
教你如何在 Javascript 文件里使用 .Net MVC Razor 语法
2014/07/23 Javascript
推荐8款jQuery轻量级树形Tree插件
2014/11/12 Javascript
详谈JavaScript内存泄漏
2014/11/14 Javascript
JavaScript原生对象之Date对象的属性和方法详解
2015/03/13 Javascript
浅析JS异步加载进度条
2016/05/05 Javascript
jQuery height()、innerHeight()、outerHeight()函数的区别详解
2016/05/23 Javascript
基于Bootstrap漂亮简洁的CSS3价格表(附源码下载)
2017/02/28 Javascript
vue项目中的webpack-dev-sever配置方法
2017/12/14 Javascript
浅谈vue-router 路由传参的方法
2017/12/27 Javascript
JS实现HTML页面中动态显示当前时间完整示例
2018/07/30 Javascript
为vue项目自动设置请求状态的配置方法
2019/06/09 Javascript
JS实现的tab切换并显示相应内容模块功能示例
2019/08/03 Javascript
[00:33]2016完美“圣”典风云人物:BurNIng宣传片
2016/12/10 DOTA
Python生成pdf文件的方法
2014/08/04 Python
浅谈Python中的可迭代对象、迭代器、For循环工作机制、生成器
2019/03/11 Python
django如何通过类视图使用装饰器
2019/07/24 Python
简单了解python调用其他脚本方法实例
2020/03/26 Python
python 实现rolling和apply函数的向下取值操作
2020/06/08 Python
学python需要去培训机构吗
2020/07/01 Python
Python基础教程(一)——Windows搭建开发Python开发环境
2020/07/20 Python
KARATOV珠宝在线商店:俄罗斯珠宝品牌
2019/03/13 全球购物
企业宣传工作方案
2014/06/02 职场文书
房展策划方案
2014/06/07 职场文书
三方股份合作协议书
2014/10/13 职场文书
大学生学习新党章思想汇报
2014/10/25 职场文书
2014年林业工作总结
2014/12/05 职场文书
平凡的世界读书笔记
2015/06/25 职场文书
2015年秋季小学开学标语
2015/07/16 职场文书
2016年社区“6.26”禁毒日宣传活动总结
2016/04/05 职场文书