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 视图(View)原理解析
May 19 MySQL
如何使用分区处理MySQL的亿级数据优化
Jun 18 MySQL
MySQL GRANT用户授权的实现
Jun 18 MySQL
mysql脏页是什么
Jul 26 MySQL
Mysql忘记密码解决方法
Feb 12 MySQL
一次SQL如何查重及去重的实战记录
Mar 13 MySQL
MySQL的存储过程和相关函数
Apr 26 MySQL
MySQL 执行数据库更新update操作的时候数据库卡死了
May 02 MySQL
MySQL GTID复制的具体使用
May 20 MySQL
mysql实现将字符串字段转为数字排序或比大小
Jun 14 MySQL
MySQL数据库查询之多表查询总结
Aug 05 MySQL
MySql按时,天,周,月进行数据统计
Aug 14 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
第七节 类的静态成员 [7]
2006/10/09 PHP
一个用于网络的工具函数库
2006/10/09 PHP
PHP函数学习之PHP函数点评
2012/07/05 PHP
AJAX PHP无刷新form表单提交的简单实现(推荐)
2016/09/09 PHP
PHP对象克隆clone用法示例
2016/09/28 PHP
ThinkPHP实现的rsa非对称加密类示例
2018/05/29 PHP
PHP设计模式入门之迭代器模式原理与实现方法分析
2020/04/26 PHP
extJs 下拉框联动实现代码
2010/04/09 Javascript
jqTransform form表单美化插件使用方法
2012/07/05 Javascript
js对象的复制继承实例
2015/01/10 Javascript
javascript实现html页面之间参数传递的四种方法实例分析
2015/12/15 Javascript
JS判断元素是否在数组内的实现代码
2016/03/30 Javascript
javascript css红色经典选项卡效果实现代码
2016/05/17 Javascript
js轮播图代码分享
2016/07/14 Javascript
使用AngularJS 跨站请求如何解决jsonp请求问题
2017/01/16 Javascript
JS实现登录页密码的显示和隐藏功能
2017/12/06 Javascript
vue计算属性和监听器实例解析
2018/05/10 Javascript
Layer弹出层动态获取数据的方法
2018/08/20 Javascript
微信小程序实现星星评价效果
2018/11/02 Javascript
Vue表情输入组件 微信face表情组件
2019/02/11 Javascript
微信小程序生成二维码的示例代码
2019/03/29 Javascript
Python使用当前时间、随机数产生一个唯一数字的方法
2017/09/18 Python
Django的CVB实例详解
2020/02/10 Python
Macbook安装Python最新版本、GUI开发环境、图像处理、视频处理环境详解
2020/02/17 Python
python实现查找所有程序的安装信息
2020/02/18 Python
使用ITK-SNAP进行抠图操作并保存mask的实例
2020/07/01 Python
html5 Canvas画图教程(9)—canvas中画出矩形和圆形
2013/01/09 HTML / CSS
美国在线医疗分销商:MedEx Supply
2020/02/04 全球购物
简述数组与指针的区别
2014/01/02 面试题
《小猪家的桃花树》教学反思
2014/04/11 职场文书
2014年教师节红领巾广播稿
2014/09/10 职场文书
2014向国旗敬礼网上签名活动总结
2014/09/27 职场文书
2014年老干部工作总结
2014/11/21 职场文书
检讨书模板大全
2015/05/07 职场文书
学校百日安全活动总结
2015/05/07 职场文书
Django实现在线无水印抖音视频下载(附源码及地址)
2021/05/06 Python