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 20 MySQL
MySQL中distinct与group by之间的性能进行比较
May 26 MySQL
MySQL 百万级数据的4种查询优化方式
Jun 07 MySQL
sql注入教程之类型以及提交注入
Aug 02 MySQL
Mysql排序的特性详情
Nov 01 MySQL
MySQL中order by的使用详情
Nov 17 MySQL
MySQL图形化管理工具Navicat安装步骤
Dec 04 MySQL
MySQL七大JOIN的具体使用
Feb 28 MySQL
分享MySQL常用 内核 Debug 几种常见方法
Mar 17 MySQL
MySQL数据库中的锁、解锁以及删除事务
May 06 MySQL
MySQL范围查询优化的场景实例详解
Jun 10 MySQL
MySQL池化框架学习接池自定义
Jul 23 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版国家代码、缩写查询函数代码
2011/08/14 PHP
php metaphone()函数的定义和用法
2016/05/15 PHP
laravel 5.3中自定义加密服务的方案详解
2017/05/09 PHP
javascript模拟的Ping效果代码 (Web Ping)
2011/03/13 Javascript
浅谈javascript的原型继承
2012/07/25 Javascript
自动刷新网页,自动刷新当前页面,JS调用
2013/06/24 Javascript
document.documentElement和document.body区别介绍
2013/09/16 Javascript
JavaScript获得url查询参数的方法
2015/07/02 Javascript
轻松学习jQuery插件EasyUI EasyUI实现树形网络基本操作(2)
2015/11/30 Javascript
javascript超过容器后显示省略号效果的方法(兼容一行或者多行)
2016/07/14 Javascript
jQuery选择器总结之常用元素查找方法
2016/08/04 Javascript
基于jQuery插件jqzoom实现的图片放大镜效果示例
2017/01/23 Javascript
jquery设置css样式的多种方法(总结)
2017/02/21 Javascript
JS实现身份证输入框的输入效果
2017/08/21 Javascript
Bootstrap Table快速完美搭建后台管理系统
2017/09/20 Javascript
js经验分享 JavaScript反调试技巧
2018/03/10 Javascript
详解React 服务端渲染方案完美的解决方案
2018/12/14 Javascript
vueScroll实现移动端下拉刷新、上拉加载
2019/03/22 Javascript
vue中配置scss全局变量的步骤
2020/12/28 Vue.js
[01:31]完美与DOTA2历程
2014/07/31 DOTA
简介Python设计模式中的代理模式与模板方法模式编程
2016/02/02 Python
使用Python的Flask框架表单插件Flask-WTF实现Web登录验证
2016/07/12 Python
python tornado微信开发入门代码
2018/08/24 Python
深入理解Python中的 __new__ 和 __init__及区别介绍
2018/09/17 Python
python的time模块和datetime模块实例解析
2019/11/29 Python
Pytorch实现基于CharRNN的文本分类与生成示例
2020/01/08 Python
Python 简单计算要求形状面积的实例
2020/01/18 Python
Autopep8的使用(python自动编排工具)
2021/03/02 Python
CSS3 旋转立方体问题详解
2020/01/09 HTML / CSS
波兰家居饰品和厨房配件网上商店:Maleomi
2020/12/15 全球购物
求职推荐信
2013/10/28 职场文书
区优秀教师事迹材料
2014/02/10 职场文书
2014年五一劳动节社区活动总结
2014/04/14 职场文书
水利水电建筑施工应届生求职信
2014/07/04 职场文书
工程部经理岗位职责
2015/02/02 职场文书
学校食堂食品安全承诺书
2015/04/29 职场文书