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 MVCC机制原理详解
Apr 20 MySQL
MySQL 如何分析查询性能
May 12 MySQL
Mysql数据库索引面试题(程序员基础技能)
May 31 MySQL
zabbix监控mysql的实例方法
Jun 02 MySQL
MySQL 使用索引扫描进行排序
Jun 20 MySQL
MySQL配置主从服务器(一主多从)
Aug 07 MySQL
MySQL命令无法输入中文问题的解决方式
Aug 30 MySQL
MySQL窗口函数的具体使用
Nov 17 MySQL
深入讲解数据库中Decimal类型的使用以及实现方法
Feb 15 MySQL
MySQL日期时间函数知识汇总
Mar 17 MySQL
MySQL分区表管理命令汇总
Mar 21 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
农民C键的运用技巧
2020/03/04 星际争霸
PHP基础教程(php入门基础教程)一些code代码
2013/01/06 PHP
基于php下载文件的详解
2013/06/02 PHP
Netbeans 8.2将支持PHP7 更精彩
2016/06/13 PHP
在PHP 7下安装Swoole与Yar,Yaf的方法教程
2017/06/02 PHP
ExtJS 学习专题(一) 如何应用ExtJS(附实例)
2010/03/11 Javascript
js中数组排序sort方法的原理分析
2014/11/20 Javascript
超赞的动手创建JavaScript框架的详细教程
2015/06/30 Javascript
js+canvas绘制矩形的方法
2016/01/28 Javascript
AngularJS教程之MVC体系结构详解
2016/08/16 Javascript
React Native预设占位placeholder的使用
2017/09/28 Javascript
Node.js学习之地址解析模块URL的使用详解
2017/09/28 Javascript
详解Webpack实战之构建 Electron 应用
2017/12/25 Javascript
Vue中的无限加载vue-infinite-loading的方法
2018/04/08 Javascript
JQuery元素快速查找与操作
2018/04/22 jQuery
jquery实现的分页显示功能示例
2019/08/23 jQuery
javscript 数组扁平化的实现
2020/02/03 Javascript
JavaScript原型继承和原型链原理详解
2020/02/04 Javascript
分析并输出Python代码依赖的库的实现代码
2015/08/09 Python
python+pyqt实现右下角弹出框
2017/10/26 Python
Python 中Pickle库的使用详解
2018/02/24 Python
python3下使用cv2.imwrite存储带有中文路径图片的方法
2018/05/10 Python
flask session组件的使用示例
2018/12/25 Python
Django框架文件上传与自定义图片上传路径、上传文件名操作分析
2019/05/10 Python
Python 实现数据结构中的的栈队列
2019/05/16 Python
python实现五子棋人机对战游戏
2020/03/25 Python
python怎么对数字进行过滤
2020/07/05 Python
英国花园家具中心:Garden Furniture Centre
2017/08/24 全球购物
Linux如何压缩可执行文件
2013/10/21 面试题
力学专业毕业生自荐信
2013/11/17 职场文书
致裁判员加油稿
2014/02/08 职场文书
求职简历自荐信
2014/06/18 职场文书
2014新生大学四年计划书
2014/09/21 职场文书
python中sqllite插入numpy数组到数据库的实现方法
2021/06/21 Python
基于Python实现流星雨效果的绘制
2022/03/18 Python
Python数组变形的几种实现方法
2022/05/30 Python