MySQL DBA如何"土土"地利用源码解决没有遇到过的错误?(1615错误)

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: MySQL DBA如何"土土"地利用源码解决没有遇到过的错误?(1615错误)

03.MySQL DBA如何"土土"地利用源码解决没有遇到过的错误?(1615错误)

本篇文章记录的是遇到一个未知错误的排查过程,由于本人水平有限,有描述不正确的欢迎指正。

问题描述

开发报错

img

MySQL error code 1615 (ER_NEED_REPREPARE): Prepared statement needs to be re-prepared

排查过程

咋一看,没见过这个错误啊,用大腿想了下这个应该是php程序为了防止SQL注入用的prepare执行的。赶紧官方bug搜了一下,一通操作以后路由到了如下地址:https://dev.mysql.com/doc/refman/5.5/en/statement-repreparation.html

简单看了一下,大概意思就是after prepare before execute的阶段,对应的表进行了DDL或者FLUSH TABLES以后table definition cache里面的metadata信息发生了改变,需要reprepare

接着我搜了一下源码,关键字

re-prepare

,然后我看到官方test套件里有相关的测试

img

可以看到对应的worklog为4166 拿到worklog id以后,我赶紧去官方的work log下搜,在High Level Architecture标签下,我注意到了下面几行

Prepared_statement::execute_loop() -- try to execute
a statement, reprepare in case of validation error, and try
again until MAX_REPREPARE_ATTEMPTS has been reached.
mysql_{sql_}stmt_execute() are changed to invoke this
method instead of plain execute().
Prepared_statement::reprepare() -- reprepare a prepared
statement, called from execute_loop() in case of ER_NEED_REPREPARE
error.

找到了对应的入口函数: Prepared_statement::execute_loop() 主要抛出错误位置如下:

if ((sql_command_flags[lex->sql_command] & CF_REEXECUTION_FRAGILE) &&
      error && !thd->is_fatal_error && !thd->killed &&
      // reprepare观察者发现invalidated,尝试MAX_REPREPARE_ATTEMPTS后报错ER_NEED_REPREPARE
      reprepare_observer.is_invalidated() &&
      reprepare_attempt++ < MAX_REPREPARE_ATTEMPTS)
  {
    DBUG_ASSERT(thd->get_stmt_da()->mysql_errno() == ER_NEED_REPREPARE);
    thd->clear_error();
    error= reprepare();
    if (! error)                                /* Success */
      goto reexecute;
  }

注意一下观察者Reprepare_observer定义

/**
Reprepare_observer观察者是用来观察某个表从上一次执行后的版本变化  
这里的"table"可以是MySQL表、临时表、视图或者information schema的表
当我们执行prepared SQL进行打开表并加锁的时候,必须要确认表没有发生改变(DML除外)。因为如果从上次prepare后表发生了改变,那么解析树可能就失效了,例如它可能包含了基于表metadata的优化。
  @sa check_and_update_table_version() 获取版本跟踪算法
*/
class Reprepare_observer
{
public:
  /**
  检查meatadata是否有变化
  */
  bool report_error(THD *thd);
  bool is_invalidated() const { return m_invalidated; }
  void reset_reprepare_observer() { m_invalidated= FALSE; }
private:
  bool m_invalidated;
};

注释里写的非常明显,

check_and_update_table_version() for details of the
  version tracking algorithm

所以我们主要目光聚集在函数check_and_update_table_version,其定义如下:

/**
这里需要比较table definition cache中的元数据版本和之前prepare生成的parse tree中的node进行版本对比
 
*/
static bool
check_and_update_table_version(THD *thd,
                               TABLE_LIST *tables, TABLE_SHARE *table_share)
{
   // 如果table_id != prepare时的table id,抛出错误,如果是prepare时期,虽然也不匹配,但是这个时候并没有观察者,也就不会抛出错误,但是到execute时,已经有了观察者,这个时候不匹配的话,就会抛出错误了
  if (! tables->is_table_ref_id_equal(table_share))
  {
    Reprepare_observer *reprepare_observer= thd->get_reprepare_observer();
    if (reprepare_observer &&
        reprepare_observer->report_error(thd))
    {
      /*
        版本不匹配,抛出错误,返回TRUE
      */
      DBUG_ASSERT(thd->is_error());
      return TRUE;
    }
    /* 根据table definition cache中的table id更新,总是维护最新的 */
    tables->set_table_ref_id(table_share);
  }
  DBUG_EXECUTE_IF("reprepare_each_statement", return inject_reprepare(thd););
  return FALSE;
}

从函数check_and_update_table_version中可以看出来,在prepare和execute之间这段时间内,如果table_ref_id(这里的table id其实就是binlog里面的table map event里面的table id,是可以变化的)如果发生了变化,那么需要reprepare。其中还有一点需要注意的是,在prepare之后,会释放对应的MDL锁,所以这个时候是可以进行DDL操作的。那么问题来了,什么情况下,这个table id会发生变化呢?

  1. DDL(包括ALTER/RENAME/TRUNCATE等)
  2. FLUSH TABLES显式地将表定义刷出缓存
  3. TABLE_DEFINITION_CACHE太小,导致对应的表定义缓存被刷出

以上根据自己的经验不完全统计。。。

关于table id

img

用户查询一个表的数据时,首先会构造根据库名、表名等信息构造hash key,然后从table_def_cache这个hash map中找是否有对应表的缓存,如果存在的话,实例化TABLE_SHARE结构体为TABLE,跟用户交互。如果不存在的话,那么会获取库名、表名等信息存入TABLE_SHARE结构体,在这里生成table_id。 生成table_id的函数在

static Table_id last_table_id;
void assign_new_table_id(TABLE_SHARE *share)
{
  DBUG_ENTER("assign_new_table_id");
  /* Preconditions */
  DBUG_ASSERT(share != NULL);
  mysql_mutex_assert_owner(&LOCK_open);
  DBUG_EXECUTE_IF("dbug_table_map_id_500", last_table_id= 500;);
  DBUG_EXECUTE_IF("dbug_table_map_id_4B_UINT_MAX+501",
                  last_table_id= 501ULL + UINT_MAX;);
  DBUG_EXECUTE_IF("dbug_table_map_id_6B_UINT_MAX",
                  last_table_id= (~0ULL >> 16););
  share->table_map_id= last_table_id++;
  DBUG_PRINT("info", ("table_id=%llu", share->table_map_id.id()));
  DBUG_VOID_RETURN;
}

过程模拟

本模拟案例由重庆八怪提供,非常感谢

session1 session2
prepare xxx
flush tables;
execute tttt;ERROR 1615(HY000): Prepared statement need be re-prepared

调试过程:

img

这里我们只需要将reprepare_attempt < MAX_REPREPARE_ATTEMPTS 改为不满足条件即可因此 修改reprepare_attempt变量为3则,reprepare_attempt < MAX_REPREPARE_ATTEMPTS 返回false 进入报错流程而不会重新加载table

总结:

为解决上述的1615问题,可以通过以下办法:

  1. 增加table_definition_cache,防止表定义被刷出缓存
  2. 增加MAX_REPREPARE_ATTEMPTS次数,但是这个属于hard code,没法通过参数修改
  3. 没事别FLUSH TABLES...(如备份,包括extrabackup和mysqldump获取一致性位点都会做FTWRL,因此建议专门的从库做备份)

这个问题的本质就是table share 在 prepare 和 execute 之间被重新加载了多次 伪代码逻辑如下:

prepare:reprepare_attempt=0 MAX_REPREPARE_ATTEMPTS=3 :
execute:
     如果table table被重置过了那么 reprepare_attempt +=1
     loop:
        重新准备 reprepare
        再次 execute
        如果table table被重置过了继续循环 reprepare_attempt +=1,如果reprepare_attempt>=MAX_REPREPARE_ATTEMPTS 这报错
        否则正常退出循环,正常执行
     end loop

实际会进行3次执行尝试,如果都失败就会报错。因此自己模拟的话还是比较难模拟的,除非直接gdb set 变量,只有在线上高压力下才可能出现。

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
监控 数据可视化 关系型数据库
微服务架构+Java+Spring Cloud +UniApp +MySql智慧工地系统源码
项目管理:项目名称、施工单位名称、项目地址、项目地址、总造价、总面积、施工准可证、开工日期、计划竣工日期、项目状态等。
305 6
|
3月前
|
JavaScript 关系型数据库 MySQL
基于JavaWeb和mysql实现校园订餐前后台管理系统(源码+数据库)
基于JavaWeb和mysql实现校园订餐前后台管理系统(源码+数据库)
|
3月前
|
NoSQL 关系型数据库 MySQL
基于Python和mysql开发的智慧校园答题考试系统(源码+数据库+程序配置说明书+程序使用说明书)
基于Python和mysql开发的智慧校园答题考试系统(源码+数据库+程序配置说明书+程序使用说明书)
|
3月前
|
NoSQL Java 关系型数据库
基于java swing和mysql实现的汽车租赁管理系统(源码+数据库+文档+运行指导视频)
基于java swing和mysql实现的汽车租赁管理系统(源码+数据库+文档+运行指导视频)
|
3月前
|
NoSQL Java 关系型数据库
基于Java swing和mysql实现酒店管理系统(源码+数据库+运行指导视频+系统用户使用手册+系统PPT+数据库设计说明书+系统概要说明书+需求说明书+详细说明书)
基于Java swing和mysql实现酒店管理系统(源码+数据库+运行指导视频+系统用户使用手册+系统PPT+数据库设计说明书+系统概要说明书+需求说明书+详细说明书)
|
3月前
|
NoSQL 关系型数据库 MySQL
基于Python和mysql开发的BBS问答社区管理系统(源码+数据库+程序配置说明书+程序使用说明书)
基于Python和mysql开发的BBS问答社区管理系统(源码+数据库+程序配置说明书+程序使用说明书)
|
3月前
|
前端开发 IDE Java
基于Springboot+MYSQL+Maven实现的宠物医院管理系统(源码+数据库+运行指导文档+项目运行指导视频)
基于Springboot+MYSQL+Maven实现的宠物医院管理系统(源码+数据库+运行指导文档+项目运行指导视频)
163 0
|
1天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
10天前
|
监控 数据可视化 安全
智慧工地SaaS可视化平台源码,PC端+APP端,支持二开,项目使用,微服务+Java++vue+mysql
环境实时数据、动态监测报警,实时监控施工环境状态,有针对性地预防施工过程中的环境污染问题,打造文明生态施工,创造绿色的生态环境。
8 0
智慧工地SaaS可视化平台源码,PC端+APP端,支持二开,项目使用,微服务+Java++vue+mysql
|
10天前
|
监控 安全 关系型数据库
基于vue2 + element +mysql医院不良事件上报系统源码
不良事件管理系统从时间上报、PDCA分析、事件整改、评估效果实行闭环管理和分析,满足医院追根溯源,全流程闭环管理,提高不良事件上报率,减少同类不良事件发生,提高医疗安全。通过报告不良事件,及时发现潜在的不安全因素
18 1