Golang使用sqlx报错max_prepared_stmt_count超过16382

文章目录

    • 背景
    • mysql的预处理
      • 查看实例预处理详情
        • com_stmt_prepare
        • 开启performance_schema
      • 本地查看预处理语句
    • 预处理语句飙升的原因
      • 生成预处理语句但是不close
      • 执行sql过程中发生错误
    • go服务分析
      • 抓包分析发送给mysql的包
      • debug查看预处理细节
        • sqlx发送statement command指令
        • sqlx关闭stmt的close
      • 怎么才能不使用预处理语句
    • 结论
    • 解决方案

背景

线上的跑的go服务操作mysql突然报错导致服务不可用,错误信息如下:

MySQL error: 1461 "Can't create more than max_prepared_stmt_count statements (current value: 16382)"

max_prepared_stmt_count是MySQL的一个基本参数,其是用来限制一个session内最多可以有多少条预处理语句,默认大小限制是16382。

mysql> show variables like 'max_prepared_stmt_count';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| max_prepared_stmt_count | 16382 |
+-------------------------+-------+

从报错信息上来看,是当前实例的预处理语句达到了16382的上限导致。

go服务使用的sql库是sqlx,数据库版本是mysql5.7.4

mysql的预处理

预处理把sql语句和参数区分开,编译是针对sql语句的编译,配合参数进行实际的sql操作。

  1. 一次编译、多次运行,提升性能
  2. 防止sql注入,参数不参与sql语句的编译
  3. 防止ddos攻击,上限16382

参考:如何定位和处理预编译语句(prepared statements)数量超限的问题 - 掘金

查看实例预处理详情

com_stmt_prepare

com_xx是mysql中的语句计数器变量,指每个语句已执行的次数。只要执行准备语句 API 调用(例如 mysql_stmt_prepare()、 **mysql_stmt_execute()**等),它们的值就会增加。

Com_stmt_close prepare语句关闭的次数
Com_stmt_execute prepare语句执行的次数
Com_stmt_prepare prepare语句创建的次数

Com_stmt_prepare 减去 Com_stmt_close 大于 max_prepared_stmt_count 就会出现这种错误。最简单的解决的方案就是调大max_prepared_stmt_count的值,但治标不治本。
参考:max_prepared_stmt_count 问题与Sysbench 工具简介

而且计数器只能看到数量,看不到细节。max_prepared_stmt_count是数据库实例级别的变量,会影响到所有用到这个数据库实例的服务。如果要看具体哪个sql语句哪个客户端导致的预处理语句飙升,那么就要看下面的prepared_statements_instances表了。

线上数据库的com_stmt_prepare
image.png

可以看到prepare 和close的值是一致的,代表执行的prepare语句都被close掉了,虽然com_stmt_prepare的值超过了16382,但实际上并不会抛出错误。

注:

  1. 每次在RDS上执行sql也会导致com_stmt_prepare和com_stmt_close的值提升。
  2. GLOBAL STATUS除非重新启动,否则无法重置计数器。重启服务也不会重置计数器。

开启performance_schema

performance_schema是mysql的一个系统库,主要记录资源的消耗,资源等待等记录。performance_schema的prepared_statements_instances表中也记录了预处理语句。

1、show variables like 'performance_schema';
开启需要设置成ON,参考:https://blog.csdn.net/weixin_41275260/article/details/125461160
2、设置 performance_schema_max_prepared_statements_instances
通过这个参数控制表的大小,可以设置成<=16283

注: 目前线上服务没有开启performance_schema,看不到故障现场的预处理语句情况。

本地查看预处理语句

手动执行prepare,发现prepare表中存在了该记录
image.png
此时查看prepare和close的值,发现这个prepare语句没有被close掉,因为正在被使用。
image.png

预处理语句飙升的原因

生成预处理语句但是不close

模拟只生成prepare语句,但是不close的情况。模拟结果也是mysql抛出错误,无法再创建预处理语句。

mysql> show global status like 'Com_stmt_%';
+-------------------------+---------+
| Variable_name           | Value   |
+-------------------------+---------+
| Com_stmt_execute        | 2437392 |
| Com_stmt_close          | 2437392 |
| Com_stmt_fetch          | 0       |
| Com_stmt_prepare        | 2453774 |
| Com_stmt_reset          | 0       |
| Com_stmt_send_long_data | 0       |
| Com_stmt_reprepare      | 0       |
+-------------------------+---------+Com_stmt_prepare - Com_stmt_close = 16382

执行sql过程中发生错误

模拟15次sql语句语法错误,此时查看Com_stmt_prepare如下:

mysql> SHOW global STATUS LIKE 'Com_stmt_%';
+-------------------------+---------+
| Variable_name           | Value   |
+-------------------------+---------+
| Com_stmt_execute        | 23 |
| Com_stmt_close          | 23 |
| Com_stmt_fetch          | 0       |
| Com_stmt_prepare        | 38 |
| Com_stmt_reset          | 0       |
| Com_stmt_send_long_data | 0       |
| Com_stmt_reprepare      | 0       |
+-------------------------+---------+prepare - close = 15

mysql官网解释如下:

即使预准备语句参数未知或执行期间发生错误, 所有变量也会增加。换句话说,它们的值对应于发出的请求数,而不是成功完成的请求数。例如,由于状态变量是在每次服务器启动时初始化的,并且不会在重新启动后持续存在,因此 跟踪 和 语句的 和 变量 的 值 通常为零,但如果 或 语句已执行但失败,则可以为非零值

mysql官方文档地址:https://dev.mysql.com/doc/refman/8.0/en/server-status-variables.html#statvar_Com_xxx

正常的prepare语句可以随着连接释放而断开,因mysql错误导致的prepare语句没有被close掉,这种是释放不了的。

go服务分析

抓包分析发送给mysql的包

抓包发现每次sql会发送三个指令,一个是prepare包含sql语句,一个是exec执行命令,一个是close statement指令。如下所示:
image.png

debug查看预处理细节

go服务中使用的查询语句主要是Get()和Select()方法。

sqlx发送statement command指令

func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {if stmt.mc.closed.Load() {errLog.Print(ErrInvalidConn)return nil, driver.ErrBadConn}	
// Send commanderr := stmt.writeExecutePacket(args)if err != nil {return nil, stmt.mc.markBadConn(err)}mc := stmt.mc// Read ResultresLen, err := mc.readResultSetHeaderPacket()if err != nil {return nil, err}
}

sqlx关闭stmt的close


func (rs *Rows) close(err error) error {
// 忽略if rs.closeStmt != nil {rs.closeStmt.Close()}
// 忽略return err
}closeStmt哪来的?
func (db *DB) queryDC(){var si driver.Stmtvar err errorwithLock(dc, func() {si, err = ctxDriverPrepare(ctx, dc.ci, query)})if err != nil {releaseConn(err)return nil, err}// ds := &driverStmt{Locker: dc, si: si}rowsi, err := rowsiFromStatement(ctx, dc.ci, ds, args...)if err != nil {ds.Close()releaseConn(err)return nil, err}
}
func (stmt *mysqlStmt) Close() error {if stmt.mc == nil || stmt.mc.closed.Load() {// driver.Stmt.Close can be called more than once, thus this function// has to be idempotent.// See also Issue #450 and golang/go#16019.//errLog.Print(ErrInvalidConn)return driver.ErrBadConn}err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id)stmt.mc = nilreturn err
}

理论上来说,我们想要对重复的sql省去编译的时间,那么是不是不应该每次都close()? 如果不主动close那么就只能等待连接释放来关闭prepare语句了?

go官方在14年回答过这个问题,需要执行stmt.close()来释放资源。https://groups.google.com/g/golang-nuts/c/ISh22XXze-s
go官方文档也说:确保stmt.Close在代码完成语句时调用它。这将释放可能与其关联的任何数据库资源(例如底层连接)。对于函数中仅是局部变量的语句,使用defer stmt.Close()就足够了。
https://go.dev/doc/database/prepared-statements
database/sql中也有一段话:由于语句在原始连接繁忙时会根据需要重新准备,因此数据库的高并发使用可能会导致大量连接繁忙,从而创建大量准备好的语句。这可能会导致明显的语句泄漏,准备和重新准备语句的频率比您想象的要高,甚至会遇到服务器端语句数量的限制。
Using Prepared Statements

怎么才能不使用预处理语句

func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {// 这里判断参数是否为0,是否有占位符,没有占位符则发送给mysql的包没有stmt模版if len(args) != 0 {if !mc.cfg.InterpolateParams {return nil, driver.ErrSkip}// try client-side prepare to reduce roundtripprepared, err := mc.interpolateParams(query, args)if err != nil {return nil, err}query = prepared}
}

这么看起来,go官方的实现就是只要sql中有占位符就默认使用预处理的方式,然后close掉。 如果sql语句不包含占位符,则直接发送sql语句给mysql服务器。

结论

  1. sql语句执行失败、没有主动close stmt都会导致mysql实例中预处理语句数量的飙升
  2. go服务中带占位符的sql会自动生成和关闭预处理语句,没有占位符的sql则不使用预处理语句
  3. 可通过Com_stmt_prepare - Com_stmt_close观测实例中的预处理语句数量

解决方案

  1. 通过报警提前发现
  2. 运维开启性能追踪库,可排查具体导致预处理语句飙升的sql以及数据库
  3. 服务的连接导致的预处理语句没close,可以重启服务解决。mysql server内部执行错误导致的预处理没close则需要重启数据库,并排查服务减少错误sql的产生
  4. 尽量不要太多服务使用一个数据库实例,如果都使用预处理语句的话,16382的上限也不算多

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/133727.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第6章_瑞萨MCU零基础入门系列教程之串行通信接口(SCI)

本教程基于韦东山百问网出的 DShanMCU-RA6M5开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id728461040949 配套资料获取&#xff1a;https://renesas-docs.100ask.net 瑞萨MCU零基础入门系列教程汇总&#xff1a; ht…

Kafka3.0.0版本——消费者(Range分区分配策略以及再平衡)

目录 一、Range分区分配策略原理1.1、Range分区分配策略原理的示例一1.2、Range分区分配策略原理的示例二1.3、Range分区分配策略原理的示例注意事项 二、Range 分区分配策略代码案例2.1、创建带有4个分区的fiveTopic主题2.2、创建三个消费者 组成 消费者组2.3、创建生产者2.4、…

PowerShell脚本免杀/bypass/绕过杀毒软件,ReconFTW 漏洞扫描

PowerShell脚本免杀/bypass/绕过杀毒软件&#xff0c;ReconFTW 漏洞扫描。 #################### 免责声明&#xff1a;工具本身并无好坏&#xff0c;希望大家以遵守《网络安全法》相关法律为前提来使用该工具&#xff0c;支持研究学习&#xff0c;切勿用于非法犯罪活动&#…

STM32 FreeRTOS 内存问题

1. STM32L151C8T6 内存&#xff0c;64Kb 的Flash&#xff08;代码就是烧录在这里面的&#xff09;&#xff0c;16Kb 的RAM&#xff0c;程序跑起来之后的内存&#xff0c;相当于我们高考时发的草稿纸&#xff0c;直接影响程序的运行速度&#xff0c;可以用STM32 CubeMx 软件直接…

【Linux】常用工具(上)

Linux 常用工具 一、Linux 软件包管理器 yum1. 软件包2. 查看软件包3. 安装/卸载软件4. yum 其他指令的功能 二、Linux 编辑器 - vim 使用1. vim 的基本概念2. vim 的基本操作&#xff08;1&#xff09;光标移动&#xff08;命令模式&#xff09;&#xff08;2&#xff09;光标…

性能测试之压力测试

文章目录 一.基本介绍二.性能指标三.下载安装JMeter1.下载安装包2.启动JMeter 四.使用JMeter1.模拟用户请求2.填写测试地址3.接收测试结果4.结果解释 一.基本介绍 压力测试考察当前软硬件条件下系统所能承受的最大负荷并找到系统瓶颈所在。压测是为了系统在线上的处理能力和稳定…

Webserver项目解析

一.webserver的组成部分 Buffer类 用于存储需要读写的数据 Channel类 存储文件描述符和相应的事件&#xff0c;当发生事件时&#xff0c;调用对应的回调函数 ChannelMap类 Channel数组&#xff0c;用于保存一系列的Channel Dispatcher 监听器&#xff0c;可以设置为epo…

【数据结构】堆排序详解

文章目录 一、堆排序思想二、向上调整建堆排序三、向下调整建堆排序四、总结 对于什么是堆&#xff0c;堆的概念分类以及堆的向上和向下两种调整算法可见&#xff1a; 堆的创建 一、堆排序思想 int a[] { 2,3,5,7,4,6 };对于这样一个数组来说&#xff0c;要想要用堆排序对它…

学信息系统项目管理师第4版系列07_项目管理知识体系

1. 项目管理原则 1.1. 勤勉、尊重和关心他人 1.1.1. 关键点 1.1.1.1. 关注组织内部和外部的职责 1.1.1.2. 坚持诚信、关心、可信、合规原则 1.1.1.3. 秉持整体观 1.1.2. 职责 1.1.2.1. 诚信 1.1.2.2. 关心 1.1.2.3. 可信 1.1.2.4. 合规 1.2. 营造协作的项目管理团队…

vim,emacs,verilog-mode这几个到底是啥关系?

vim&#xff1a;不多说了被各类coder誉为地表最强最好用的编辑器&#xff1b;gvim&#xff0c;gui vim的意思&#xff1b; emacs&#xff1a;也是一个编辑器&#xff0c;类似vscode&#xff1b; vim在使用的时候为了增强其功能&#xff0c;有好多好多插件&#xff0c;都是以.…

eNSP网络学习

一、eNSP 1.什么是eNSP eNSP(Enterprise Network Simulation Platform)是一款由华为提供的免费的、可扩展的、图形化操作的网络仿真工具平台&#xff0c;主要对企业网络路由器、交换机进行软件仿真&#xff0c;完美呈现真实设备实景&#xff0c;支持大型网络模拟&#xff0c;让…

浅谈C/S vs. B/S的区别

目录 C/S简介: B/S简介&#xff1a; C/S-B/S区别: 1.硬件环境不同: 2.安全要求不同: 3.处理问题不同&#xff1a; 总结: C/S简介: C/S:客户机(Client)/服务器模式(Server)模型中&#xff0c;(C/S是Client/Server的缩写。客户端需要安装专用的客户端软件) 客户端和服务器…

如何在谷某地球飞行模拟中导入简单飞机开发的飞机模型

如何在谷某地球飞行模拟中导入简单飞机开发的飞机模型 简飞的飞友们&#xff01;我并没有弃坑&#xff0c;只不过我不是你们想象的那样设计飞机。我之前写过一篇图文讲解如何在谷某地球里规划飞行航线&#xff1a; 手把手教你驾驶西锐SR-22小飞机在美国大峡谷中穿行https://b…

c语言每日一练(15)

前言&#xff1a;每日一练系列&#xff0c;每一期都包含5道选择题&#xff0c;2道编程题&#xff0c;博主会尽可能详细地进行讲解&#xff0c;令初学者也能听的清晰。每日一练系列会持续更新&#xff0c;上学期间将看学业情况更新。 五道选择题&#xff1a; 1、程序运行的结果…

【C++】红黑树插入操作实现以及验证红黑树是否正确

文章目录 前言一、红黑树的插入操作1.红黑树结点的定义2.红黑树的插入1.uncle存在且为红2.uncle不存在3.uncle存在且为黑 3.完整代码 二、是否为红黑树的验证1.IsBlance函数2.CheckColor函数 三、红黑树与AVL树的比较 前言 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在…

驱动轴相机参数设置Web前端界面开发

一、基于Django的Web应用界面的开发&#xff1a; 在Realtimeresults.html上添加一个按钮组件&#xff0c;获取检测到的轴型和车轮信息&#xff0c;点击后可以获取package.json里存放的json数据&#xff0c;效果如下&#xff1a; 实现逻辑&#xff1a;需要从URL设置、视图函数、…

百度千帆大模型文心一言api调用

注册百度智能云账号并申请文心千帆大模型资格 https://login.bce.baidu.com/ https://cloud.baidu.com/product/wenxinworkshop 创建应用用于获取access_token 创建应用成功后,可以获取到API Key和Secret Key 获取access_token curl https://aip.baidubce.com/oauth/2.0/to…

Vue 3的革命性新特性:深入了解Composition API

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

Java毕业设计 SSM SpringBoot 水果蔬菜商城

Java毕业设计 SSM SpringBoot 水果蔬菜商城 SSM 水果蔬菜商城 功能介绍 首页 图片轮播 关键字搜索商品 分类菜单 折扣大促销商品 热门商品 商品详情 商品评价 收藏 加入购物车 公告 留言 登录 注册 我的购物车 结算 个人中心 我的订单 商品收藏 修改密码 后台管理 登录 商品…

【玩玩Vue】使用elementui页面布局和控制页面的滚动

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 文章目录 前言一、页面布局二、页面滚动1.禁用body的滑动2.禁用el-aside的滚动3.启动el-main的滚动 前言 一、页面布局 这里布局使用vueelementui&…