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三值逻辑与NULL
May 19 MySQL
MySQL 百万级数据的4种查询优化方式
Jun 07 MySQL
MySQL连接控制插件介绍
Sep 25 MySQL
MySQL中datetime时间字段的四舍五入操作
Oct 05 MySQL
MySQL常见优化方案汇总
Jan 18 MySQL
一次Mysql update sql不当引起的生产故障记录
Apr 01 MySQL
排查MySQL生产环境索引没有效果
Apr 11 MySQL
详细介绍MySQL中limit和offset的用法
May 06 MySQL
Mysql 一主多从的部署
May 20 MySQL
详解Mysql数据库平滑扩容解决高并发和大数据量问题
May 25 MySQL
MySQL解决Navicat设置默认字符串时的报错问题
Jun 16 MySQL
面试官问我Mysql的存储引擎了解多少
Aug 05 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
探讨:array2xml和xml2array以及xml与array的互相转化
2013/06/24 PHP
php二分查找二种实现示例
2014/03/12 PHP
php获取当月最后一天函数分享
2015/02/02 PHP
php redis实现文章发布系统(用户投票系统)
2017/03/04 PHP
PHP函数积累总结
2019/03/19 PHP
PHP实现长轮询消息实时推送功能代码实例讲解
2021/02/26 PHP
PHP7新特性
2021/03/09 PHP
jQuery使用手册之三 CSS操作
2007/03/24 Javascript
几个常用的JavaScript字符串处理函数 - split()、join()、substring()和indexOf()
2009/06/02 Javascript
jquery异步循环获取功能实现代码
2010/09/19 Javascript
jQuery EasyUI API 中文文档 - EasyLoader 加载器
2011/09/29 Javascript
深入理解JavaScript系列(12) 变量对象(Variable Object)
2012/01/16 Javascript
js实现当复选框选择匿名登录时隐藏登录框效果
2015/08/14 Javascript
图解js图片轮播效果
2015/12/20 Javascript
微信小程序使用第三方库Immutable.js实例详解
2016/09/27 Javascript
JS 终止执行的实现方法
2016/11/24 Javascript
让div运动起来 js实现缓动效果
2017/07/06 Javascript
vue2.0 父组件给子组件传递数据的方法
2018/01/15 Javascript
vue多页面开发和打包正确处理方法
2018/04/20 Javascript
jQuery阻止事件冒泡实例分析
2018/07/03 jQuery
JavaScript折半查找(二分查找)算法原理与实现方法示例
2018/08/06 Javascript
vue生命周期实例小结
2018/08/15 Javascript
详解一个小实例理解js原型和继承
2019/04/24 Javascript
JS利用prototype给类添加方法操作详解
2019/06/21 Javascript
JavaScript中window和document用法详解
2020/07/28 Javascript
Python卸载模块的方法汇总
2016/06/07 Python
python logging日志模块的详解
2017/10/29 Python
使用python远程操作linux过程解析
2019/12/04 Python
Python常用库大全及简要说明
2020/01/17 Python
解决pycharm同一目录下无法import其他文件
2020/02/12 Python
利用OpenCV中对图像数据进行64F和8U转换的方式
2020/06/03 Python
python实现简单的学生管理系统
2021/02/22 Python
接受捐赠答谢词
2014/01/27 职场文书
小学家长学校培训材料
2014/08/24 职场文书
第一军规观后感
2015/06/12 职场文书
2016年世界人口日宣传活动总结
2016/04/05 职场文书