• GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。

一、问题发现

在一次开发中使用 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,如果因为少分配了一个长度导致结束符没有存进去,最后存放的字符串就会产生问题。

Enjoy GreatSQL

本文由博客一文多发平台 OpenWrite 发布!

MySQL 的prepare使用中的bug解析过程的更多相关文章

  1. MySQL解析过程、执行过程

    转载:https://student-lp.iteye.com/blog/2152601 https://www.cnblogs.com/cdf-opensource-007/p/6502556.ht ...

  2. 年年出妖事,一例由JSON解析导致的"薛定谔BUG"排查过程记录

    前言 做开发这么多年,也碰到无数的bug了.不过再复杂的bug,只要仔细去研读代码,加上debug,总能找到原因. 但是最近公司内碰到的这一个bug,这个bug初看很简单,但是非常妖孽,在一段时间内我 ...

  3. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  4. Mysql 预处理 PREPARE以及预处理的好处

    Mysql 预处理 PREPARE以及预处理的好处 Mysql手册 预处理记载: 预制语句的SQL语法在以下情况下使用:   · 在编代码前,您想要测试预制语句在您的应用程序中运行得如何.或者也许一个 ...

  5. 【原创】大数据基础之Hive(2)Hive SQL执行过程之SQL解析过程

    Hive SQL解析过程 SQL->AST(Abstract Syntax Tree)->Task(MapRedTask,FetchTask)->QueryPlan(Task集合)- ...

  6. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  7. spring cron表达式及解析过程

    1.cron表达式 cron表达式是用来配置spring定时任务执行时间的字符串,由5个空格分隔成的6个域构成,格式如下: {秒}  {分}  {时}  {日}  {月}  {周} 每一个域的含义解释 ...

  8. 常见bug解析-移动端

    手机测试常见bug解析 1.测试时遇到“手机无响应”? 有以下几个原因: a.手机内存不足 b.android进程之间死锁引起的(就是两个进程之间) c.手机的CPU运行高引起的 可以查看手机的崩溃日 ...

  9. MySQL 利用xtrabackup进行增量备份详细过程汇总 (转)

    Xtrabackup下载.安装以及全量备份请参考:http://blog.itpub.net/26230597/viewspace-1465772/ 1,创建mysql备份用户 mysql -uroo ...

随机推荐

  1. Visual Studio Installer下载速度为0处理办法

    DNS改为:223.5.5.5即可. 223.5.5.5 下载完成后记得改回来.

  2. python常用标准库(压缩包模块zipfile和tarfile)

    常用的标准库 在我们常用的系统windows和Linux系统中有很多支持的压缩包格式,包括但不限于以下种类:rar.zip.tar,以下的标准库的作用就是用于压缩解压缩其中一些格式的压缩包. zip格 ...

  3. JavaScript产生随机颜色

    //获取rgb类型的颜色 IE7不支持 function randomColor(){ var r = Math.floor(Math.random()*256); var g = Math.floo ...

  4. bat-静默安装winrar并设置系统级环境变量

    @echo off Setlocal enabledelayedexpansion @REM vscode中自动开启延迟环境变量扩展 echo 安装winrar "winrar v.5.71 ...

  5. 利用IDEA搭建SpringBoot项目,整合mybatis

    一.配置文件.启动项目 生成之后这几个文件可以删掉的 配置application spring.datasource.url=jdbc:mysql://localhost:3306/test?serv ...

  6. windows脚本bat做文件备份

    @ECHO OFF echo 切换到当前目录... cd /d %~dp0% echo 开始复制Code1... echo d | XCOPY Code1 ..\备份\bakdir\Code1 /s ...

  7. 挑战30天写操作系统-day3-进入32位模式并导入C语言

    目录 1.制作真正的IPL IPL:启动区,启动程序装载器完整代码: ; haribote-ipl ; TAB=4 CYLS EQU 10 ; 声明CYLS=10 ORG 0x7c00 ; 指明程序装 ...

  8. DNS 系列(三):如何免受 DNS 欺骗的侵害

    互联网上每一台设备都会有一个 IP 地址,我们在访问网站或发送信息时,其实都是通过 IP 地址达成准确请求的.但是这个 IP 地址由很长一串数字组成,记忆起来相当困难,所以我们创造了更实用的域名来代替 ...

  9. Java对接拼多多开放平台API(加密上云等全流程)

    前言 本文为[小小赫下士 blog]原创,搬运请保留本段,或请在醒目位置设置原文地址和原作者. 作者:小小赫下士 原文地址:Java对接拼多多开放平台API(加密上云等全流程) 本文章为企业ERP(I ...

  10. IDEA快捷键之html篇-2

    .qa-item .qa-item-ft .icon { display: inline-block; width: 16px; height: 16px; vertical-align: sub; ...