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数据库并展示在前端页面的实现代码
May 02 MySQL
MySQL sql_mode修改不生效的原因及解决
May 07 MySQL
mysql 数据插入优化方法之concurrent_insert
Jul 01 MySQL
一次MySQL启动导致的事故实战记录
Sep 15 MySQL
浅谈如何保证Mysql主从一致
Mar 13 MySQL
MySQL读取JSON转换的方式
Mar 18 MySQL
Windows下载并安装MySQL8.0.x 版本的完整教程
Apr 10 MySQL
mysql 乱码 字符集latin1转UTF8
Apr 19 MySQL
MySQL脏读,幻读和不可重复读
May 11 MySQL
MySQL普通表如何转换成分区表
May 30 MySQL
MySQL数据库查询之多表查询总结
Aug 05 MySQL
mysql序号rownum行号实现方式
Dec 24 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初学者阅读的4本经典书籍
2016/09/23 PHP
PHP迭代器和生成器用法实例分析
2019/09/28 PHP
php判断某个方法是否存在函数function_exists (),method_exists()与is_callable()区别与用法解析
2020/04/20 PHP
jQuery调用ajax请求的常见方法汇总
2015/03/24 Javascript
js简单的点击返回顶部效果实现方法
2015/04/10 Javascript
浅谈Javascript数组(推荐)
2016/05/17 Javascript
深入浅出讲解ES6的解构
2016/08/03 Javascript
javascript 实现动态侧边栏实例详解
2016/11/11 Javascript
Vue组件开发初探
2017/02/14 Javascript
vue项目中导入swiper插件的方法
2018/01/30 Javascript
vue 右键菜单插件 简单、可扩展、样式自定义的右键菜单
2018/11/29 Javascript
微信小程序自定义toast组件的方法详解【含动画】
2019/05/11 Javascript
vue+element搭建后台小总结 el-dropdown下拉功能
2020/04/10 Javascript
如何实现vue的tree组件
2020/12/03 Vue.js
[03:18]DOTA2亚洲邀请赛小组赛第一日 RECAP赛事回顾
2015/01/30 DOTA
[59:15]EG vs LGD 2018国际邀请赛淘汰赛BO3 第一场 8.26
2018/08/29 DOTA
python将四元数变换为旋转矩阵的实例
2019/12/04 Python
python中count函数简单的实例讲解
2020/02/06 Python
Python实现常见的几种加密算法(MD5,SHA-1,HMAC,DES/AES,RSA和ECC)
2020/05/09 Python
python初步实现word2vec操作
2020/06/09 Python
python输出结果刷新及进度条的实现操作
2020/07/13 Python
一款基于css3的列表toggle特效实例教程
2015/01/04 HTML / CSS
美国知名奢侈美容品牌零售商:Cos Bar
2017/04/21 全球购物
OPPO手机官方商城:中国手机市场出货量第一品牌
2017/10/18 全球购物
Lentiamo丹麦:购买便宜的隐形眼镜
2021/01/13 全球购物
印度电子产品购物网站:Vijay Sales
2021/02/16 全球购物
大学军训感言1500字
2014/03/09 职场文书
文艺演出策划方案
2014/06/07 职场文书
送温暖献爱心活动总结
2014/07/08 职场文书
商业企业管理专业求职信
2014/07/10 职场文书
员工教育培训协议书
2014/09/27 职场文书
2014年加油站工作总结
2014/12/04 职场文书
教师个人总结范文
2015/02/11 职场文书
超市员工辞职信范文
2015/05/12 职场文书
教师研修随笔感言
2015/11/18 职场文书
党员廉政准则心得体会
2016/01/20 职场文书