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系列之三 基础篇
Jul 02 MySQL
MySql子查询IN的执行和优化的实现
Aug 02 MySQL
MySQL中utf8mb4排序规则示例
Aug 02 MySQL
mysql 索引合并的使用
Aug 30 MySQL
MySQL中几种插入和批量语句实例详解
Sep 14 MySQL
MySQL中datetime时间字段的四舍五入操作
Oct 05 MySQL
mysql sum(if())和count(if())的用法说明
Jan 18 MySQL
MySql重置root密码 --skip-grant-tables
Apr 11 MySQL
MySQL创建管理LIST分区
Apr 13 MySQL
MySql数据库触发器使用教程
Jun 01 MySQL
MySQL串行化隔离级别(间隙锁实现)
Jun 16 MySQL
SQL Server数据库的三种创建方法汇总
May 08 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
极典R601SW收音机
2021/03/02 无线电
缓存技术详谈―php
2006/12/14 PHP
用Zend Studio+PHPnow+Zend Debugger搭建PHP服务器调试环境步骤
2014/01/19 PHP
PHP AjaxForm提交图片上传并显示图片源码
2016/11/29 PHP
javascript读取xml
2006/11/04 Javascript
判断js对象是否拥有某一个属性的js代码
2013/08/16 Javascript
JQuery教学之性能优化
2014/05/14 Javascript
jquery重复提交请求的原因浅析
2014/05/23 Javascript
jQuery实现的产品自动360度旋转展示特效源码分享
2015/08/21 Javascript
学习使用grunt来打包JavaScript和CSS程序的教程
2016/01/04 Javascript
React如何利用相对于根目录进行引用组件详解
2017/10/09 Javascript
JavaScript树的深度优先遍历和广度优先遍历算法示例
2018/07/30 Javascript
说说如何使用Vuex进行状态管理(小结)
2019/04/14 Javascript
微信用户访问小程序的登录过程详解
2019/09/20 Javascript
vue基础知识--axios合并请求和slot
2020/06/04 Javascript
微信小程序实现通讯录列表展开收起
2020/11/18 Javascript
vant时间控件使用方法详解
2020/12/24 Javascript
python字符串替换的2种方法
2014/11/30 Python
Python实现的文本编辑器功能示例
2017/06/30 Python
Python开发中爬虫使用代理proxy抓取网页的方法示例
2017/09/26 Python
Python之自动获取公网IP的实例讲解
2017/10/01 Python
python实现Dijkstra静态寻路算法
2019/01/17 Python
python实现beta分布概率密度函数的方法
2019/07/08 Python
pytorch如何冻结某层参数的实现
2020/01/10 Python
python中K-means算法基础知识点
2021/01/25 Python
C语言中break与continue的区别
2012/07/12 面试题
static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
2015/02/22 面试题
专科应届生求职信
2013/11/24 职场文书
中学生个人自我评价
2014/02/06 职场文书
爱心捐款倡议书
2014/04/14 职场文书
关爱留守儿童标语
2014/06/18 职场文书
教师查摆问题及整改措施
2014/10/11 职场文书
2014年学校禁毒工作总结
2014/12/23 职场文书
个人工作失误的保证书怎么写?
2019/06/21 职场文书
linux中nohup和后台运行进程查看及终止
2021/06/24 Python
spring项目中切面及AOP的使用方法
2021/06/26 Java/Android