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 Shell的介绍以及安装
Apr 24 MySQL
MySQL查询学习之基础查询操作
May 08 MySQL
MySQL 分组查询的优化方法
May 12 MySQL
MySQL 百万级数据的4种查询优化方式
Jun 07 MySQL
MySQL空间数据存储及函数
Sep 25 MySQL
Mysql外键约束的创建与删除的使用
Mar 03 MySQL
你真的会用Mysql的explain吗
Mar 31 MySQL
数据分析数据库ClickHouse在大数据领域应用实践
Apr 03 MySQL
mysql查询结果实现多列拼接查询
Apr 03 MySQL
MySQL创建管理HASH分区
Apr 13 MySQL
MySQL GTID复制的具体使用
May 20 MySQL
mysql sock 文件解析及作用讲解
Jul 15 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&amp;mysql(一)
2006/10/09 PHP
PHP中uploaded_files函数使用方法详解
2011/03/09 PHP
用 Composer构建自己的 PHP 框架之使用 ORM
2014/10/30 PHP
php递归遍历多维数组的方法
2015/04/18 PHP
php parse_str() 函数的定义和用法
2016/05/23 PHP
php实现根据身份证获取精准年龄
2020/02/26 PHP
dropdownlist之间的互相联动实现(显示与隐藏)
2009/11/24 Javascript
jquery tab插件制作实现代码
2010/06/22 Javascript
js 创建快捷方式的代码(fso)
2010/11/19 Javascript
JS俄罗斯方块,包含完整的设计理念
2010/12/11 Javascript
JS删除数组元素的函数介绍
2013/03/27 Javascript
jQuery 借助插件Lavalamp实现导航条动态美化效果
2013/09/27 Javascript
JavaScript中对象属性的添加和删除示例
2014/05/12 Javascript
ajaxFileUpload.js插件支持多文件上传的方法
2014/09/02 Javascript
深入理解JavaScript编程中的同步与异步机制
2015/06/24 Javascript
sso跨域写cookie的一段js脚本(推荐)
2016/05/25 Javascript
nodejs操作mysql实现增删改查的实例
2017/05/28 NodeJs
浅谈react性能优化的方法
2018/09/05 Javascript
vue-cli3搭建项目的详细步骤
2018/12/05 Javascript
Ant Design moment对象和字符串之间的相互转化教程
2020/10/27 Javascript
Python的Django中django-userena组件的简单使用教程
2015/05/30 Python
浅析python,PyCharm,Anaconda三者之间的关系
2019/11/27 Python
通过CSS3的object-fit来调整图片适配尺寸的技巧简介
2016/02/27 HTML / CSS
HTML5 语义化结构化规范化
2008/10/17 HTML / CSS
La Senza官网:北美顶尖性感内衣品牌
2018/08/03 全球购物
香港士多网上超级市场:Ztore
2021/01/09 全球购物
施华洛世奇新加坡官网:SWAROVSKI新加坡
2020/10/06 全球购物
下列程序在32位linux或unix中的结果是什么
2015/01/26 面试题
中专生自我鉴定范文
2013/12/19 职场文书
个人求职信范文分享
2014/01/06 职场文书
大学生自我鉴定书
2014/03/24 职场文书
2015年大学班长个人工作总结
2015/04/24 职场文书
党员反四风学习心得体会
2016/01/22 职场文书
react antd实现动态增减表单
2021/06/03 Javascript
Python实现简繁体转换
2021/06/07 Python
Golang生成Excel文档的方法步骤
2021/06/09 Golang