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 如何分析查询性能
May 12 MySQL
MySQL 不等于的三种使用及区别
Jun 03 MySQL
MySQL中的布尔值,怎么存储false或true
Jun 04 MySQL
MySQL索引失效的典型案例
Jun 05 MySQL
MySQL8.0.18配置多主一从
Jun 21 MySQL
MySQL开启事务的方式
Jun 26 MySQL
Mysql中调试存储过程最简单的方法
Jun 30 MySQL
MySQL索引 高效获取数据的数据结构
May 02 MySQL
MySQL查询日期时间
May 15 MySQL
MySQL数据库简介与基本操作
May 30 MySQL
MySQL实现字段分割一行转多行的示例代码
Jul 07 MySQL
MySQL 原理与优化之Update 优化
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
ThinkPHP基本的增删查改操作实例教程
2014/08/22 PHP
PHP关联数组实现根据元素值删除元素的方法
2015/06/26 PHP
PHP实现简单数字分页效果
2015/07/26 PHP
PHP实现二维数组去重功能示例
2017/01/12 PHP
PHP使用数组实现矩阵数学运算的方法示例
2017/05/29 PHP
浅谈php的TS和NTS的区别
2019/03/13 PHP
几款极品的javascript压缩混淆工具
2007/05/16 Javascript
JavaScript实现拖拽网页内元素的方法
2015/04/15 Javascript
使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
2015/08/21 NodeJs
chrome调试javascript详解
2015/10/21 Javascript
js检测用户输入密码强度
2015/10/22 Javascript
JavaScript 中 avalon绑定属性总结
2016/10/19 Javascript
Javascript 引擎工作机制详解
2016/11/30 Javascript
jquery实时获取时间的简单实例
2017/01/26 Javascript
Dropify.js图片宽高自适应的方法
2017/11/27 Javascript
微信小程序自定义底部弹出框
2020/11/16 Javascript
JavaScript中工厂函数与构造函数示例详解
2019/05/06 Javascript
使用vue-router切换页面时,获取上一页url以及当前页面url的方法
2019/05/06 Javascript
监控Nodejs的性能实例代码
2019/07/02 NodeJs
js+canvas实现刮刮奖功能
2020/09/13 Javascript
pymongo为mongodb数据库添加索引的方法
2015/05/11 Python
python简单实现基于SSL的IRC bot实例
2015/06/15 Python
python下如何查询CS反恐精英的服务器信息
2017/01/17 Python
Python操作MySQL数据库的方法
2018/06/20 Python
查看python下OpenCV版本的方法
2018/08/03 Python
python批量修改图片尺寸,并保存指定路径的实现方法
2019/07/04 Python
python 利用浏览器 Cookie 模拟登录的用户访问知乎的方法
2019/07/11 Python
Django后台管理系统的图文使用教学
2020/01/20 Python
蒂芙尼澳大利亚官方网站:Tiffany&Co. Australia
2017/08/27 全球购物
农场厂长岗位职责
2013/12/28 职场文书
圣诞节红领巾广播稿
2014/02/03 职场文书
个人批评与自我批评总结
2014/10/17 职场文书
2015年基建工作总结范文
2015/05/23 职场文书
小学英语教师2015年度个人工作总结
2015/10/14 职场文书
小学生大队委竞选稿
2015/11/20 职场文书
《飘》英文读后感五篇
2019/10/11 职场文书