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获取所有分类的前N条记录
May 07 MySQL
一篇文章弄懂MySQL查询语句的执行过程
May 07 MySQL
MySQL的Query Cache图文详解
Jul 01 MySQL
MySQL实例精讲单行函数以及字符数学日期流程控制
Oct 15 MySQL
防止web项目中的SQL注入
Dec 06 MySQL
数据分析数据库ClickHouse在大数据领域应用实践
Apr 03 MySQL
SQL语句多表联合查询的方法示例
Apr 18 MySQL
Mysql开启外网访问
May 15 MySQL
详解Mysql数据库平滑扩容解决高并发和大数据量问题
May 25 MySQL
MySQL表字段数量限制及行大小限制详情
Jul 23 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
做个自己站内搜索引擎
2006/10/09 PHP
php ignore_user_abort与register_shutdown_function 使用方法
2009/06/14 PHP
网页上facebook分享功能具体实现
2014/01/26 PHP
PHP实现中国公民身份证号码有效性验证示例代码
2017/05/03 PHP
yii2安装详细流程
2018/05/23 PHP
JQuery 学习笔记 选择器之五
2009/07/23 Javascript
jquery选择器大全 全面详解jquery选择器
2014/03/06 Javascript
node.js中的path.dirname方法使用说明
2014/12/09 Javascript
深入浅析NodeJs并发异步的回调处理
2015/12/21 NodeJs
mvvm双向绑定机制的原理和实现代码(推荐)
2016/06/07 Javascript
微信小程序 css使用技巧总结
2017/01/09 Javascript
jquery Easyui Datagrid实现批量操作(编辑,删除,添加)
2017/02/20 Javascript
使用Vue.js开发微信小程序开源框架mpvue解析
2018/03/20 Javascript
Spring boot 和Vue开发中CORS跨域问题解决
2018/09/05 Javascript
解决vue同一slot在组件中渲染多次的问题
2018/09/06 Javascript
vue服务端渲染添加缓存的方法
2018/09/18 Javascript
Vue快速实现通用表单验证功能
2019/12/05 Javascript
JavaScript console的使用方法实例分析
2020/04/28 Javascript
[14:50]2018DOTA2亚洲邀请赛开幕式
2018/04/03 DOTA
python使用cookielib库示例分享
2014/03/03 Python
Python执行时间的计算方法小结
2017/03/17 Python
Python 错误和异常代码详解
2018/01/29 Python
Python使用POP3和SMTP协议收发邮件的示例代码
2019/04/16 Python
python jenkins 打包构建代码的示例代码
2019/11/29 Python
Python基于read(size)方法读取超大文件
2020/03/12 Python
PyCharm中配置PySide2的图文教程
2020/06/18 Python
CSS3色彩模式有哪些?CSS3 HSL色彩模式的定义
2016/04/26 HTML / CSS
详解CSS3实现响应式手风琴效果
2020/06/10 HTML / CSS
一文彻底解决HTML5页面中长按保存图片功能
2019/06/10 HTML / CSS
Farfetch巴西官网:奢侈品牌时尚购物平台
2020/10/19 全球购物
电大毕业生自我鉴定
2013/11/10 职场文书
2014信息技术专业毕业生自我评价
2014/01/17 职场文书
职业培训师职业生涯规划
2014/02/18 职场文书
高中升旗仪式主持词
2015/07/03 职场文书
python如何读取.mtx文件
2021/04/22 Python
python中sqllite插入numpy数组到数据库的实现方法
2021/06/21 Python