Golang使用sqlx报错max_prepared_stmt_count超过16382

news2024/9/23 19:23:25

文章目录

    • 背景
    • 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 command
	err := stmt.writeExecutePacket(args)
	if err != nil {
		return nil, stmt.mc.markBadConn(err)
	}

	mc := stmt.mc

	// Read Result
	resLen, 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.Stmt
	var err error
	withLock(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 = nil
	return 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 roundtrip
		prepared, 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.coloradmin.cn/o/1012969.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

伦敦银时走势与获利机会

交易时间灵活、资金杠杆充沛是伦敦银交易的主要优势&#xff0c;投资者应该充分利用这个品种的制度优势&#xff0c;结合自己个人的作息时间&#xff0c;在工作、投资与生活三者之间取得平衡的前提下&#xff0c;借助国际白银市场的波动&#xff0c;通过交易逐步实现自己的财富…

外贸电商商品如何做好上架工作?

跨境电商业务的蓬勃发展已经成为互联网行业的热点话题之一。不论是将海外货源卖回国内&#xff0c;还是通过国内货源销往海外&#xff0c;跨境电商平台都面临着如何实现商品上架的关键问题。在这篇文章中&#xff0c;将探讨成功上架商品的关键步骤。 一、准备好接口。 跨境电商…

【LangChain系列 10】Prompt模版——Message的partial用法

原文地址&#xff1a;【LangChain系列 10】Prompt模版——Message的partial用法 本文速读&#xff1a; 字符串partial 方法partial partial是什么意思呢&#xff1f;简单来说&#xff1a;将一个prompt模版传入部分变量值而生成一个新的prompt模版&#xff0c;当使用新的promp…

小技巧!Python生成excel文件的三种方式!

在我们做平常工作中都会遇到操作excel&#xff0c;那么今天写一篇&#xff0c;如何通过python操作excel。当然python操作excel的库有很多&#xff0c;比如pandas&#xff0c;xlwt/xlrd&#xff0c;openpyxl等&#xff0c;每个库都有不同的区别&#xff0c;具体的区别&#xff0…

递归路由,怎么递归的?BGP4+

问题 R2上去往5&#xff1a;&#xff1a;5的递归路由怎么生成的&#xff1f;&#xff1f;&#xff1f; BGP4路由表 Destination : 5:: PrefixLength : 64 NextHop : 4::4 Preference : 255 Cost : …

sed命令在Mac和Linux下的不同

问题 &#xff08;1&#xff09;Windows系统里&#xff0c;文件每行结尾是<回车><换行>, \r\n &#xff08;2&#xff09;Mac系统里&#xff0c; 文件每行结尾是<回车>&#xff0c;即\r &#xff08;3&#xff09;Unix系统里&#xff0c; 文件每行…

Sleuth--链路追踪

1 链路追踪介绍 在大型系统的微服务化构建中&#xff0c;一个系统被拆分成了许多模块。这些模块负责不同的功能&#xff0c;组合成系统&#xff0c;最终可以提供丰富的功能。在这种架构中&#xff0c;一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上&…

Python技巧---tqdm库的使用

文章目录 一、tqdm基本知识二、在pytorch中使用tqdm 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、tqdm基本知识 “tqdm” 是一个 Python 库&#xff0c;用于在命令行界面中创建进度条。 基本使用如下&#xff1a; from tqdm import tqdm impor…

linux安装nacos2.2.0

1、使用docker拉取镜像&#xff1a;docker pull nacos/nacos-server:v2.2.0 2、下载官方配置文件&#xff1a;https://github.com/alibaba/nacos/releases 3、修改配置文件的数据库连接信息&#xff0c;修改完成后将配置文件移至挂载目录/home/shixp/docker/nacos/conf&#xf…

无涯教程-JavaScript - ISNONTEXT函数

描述 如果指定的值引用的不是文本,则ISNONTEXT函数将返回逻辑值TRUE。否则返回FALSE。如果该值引用空白单元格,则该函数返回TRUE。 语法 ISNONTEXT (value)争论 Argument描述Required/OptionalvalueValue or expression or a reference to a cell.Required Notes 您可以在…

Echarts 饼图的详细配置过程

文章目录 饼图 简介配置步骤简易示例 饼图 简介 Echarts饼图是Echarts中常用的一种图表类型&#xff0c;也是数据可视化中常用的一种形式。饼图通过扇形的方式展示数据的比例和占比关系。 Echarts饼图的特点如下&#xff1a; 直观的数据占比展示&#xff1a;饼图通过不同大小…

Fast-MVSNet CVPR-2020 学习笔记总结 译文 深度学习三维重建

文章目录 6 Fast-MVSNet CVPR-20206.0 主要特点6.1 网络介绍6.2 稀疏高分辨率深度图预测6.3 深度图扩展6.4 -Newton 精细化MVSNet系列最新顶刊 对比总结6 Fast-MVSNet CVPR-2020 深度学习三维重建 Fast-MVSNet-CVPR-2020(源码、原文、译文、批注) 下载 6.0 主要特点 Spare …

数据结构与算法(三)--栈

一、前言 前两篇文章我们学习了第一个数据结构&#xff0c;数组&#xff0c;且从底层通过java实现了数组的构建和增删改查的操作功能&#xff0c;并且通过resize操作使我们的数组可以动态的扩容或者缩容。且我们知道数组最大的优点就是在索引有语义的情况下&#xff0c;查询和…

VS2013任意一个项目配置Tiff环境

1.包含目录库目录 2.链接器输出tiff.lib 3.文件夹里放一些东西

北邮22级信通院数电:Verilog-FPGA(1)实验一“跑通第一个例程” 过程中遇到的常见问题与解决方案汇总(持续更新中)

北邮22信通一枚~ 跟随课程进度更新北邮信通院数字系统设计的笔记、代码和文章 持续关注作者 迎接数电实验学习~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院数电实验_青山如墨雨如画的博客-CSDN博客 目录 问题一&#xff1a;Verilog代码没有跑通 报…

如何维护物流中心电力系统?这个方法太炸裂了

物流中心是现代供应链管理中的关键环节之一&#xff0c;它承担着货物存储、分拣和分发的任务。而物流中心的正常运营离不开可靠的电力供应。 配电柜&#xff0c;作为电力系统的关键组成部分&#xff0c;负责分配电能到各个设备和区域&#xff0c;因此其运行状态至关重要。为了确…

Docker核心原理与实操

第一章、Docker基本概念 1、概念&#xff1a;Docker是一种容器技术&#xff0c;可以解决软件跨环境迁移问题。 2、实现原理&#xff1a;是一个分层复用的文件系统&#xff1b;每一层都是一个独立的软件&#xff1b; …

前端JS中的异步编程与Promise

&#x1f3ac; 岸边的风&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 一、JavaScript的异步编步机制 二、事件循环&#xff08;Event Loop&#xff09;和任务队列&#xff08;Task Queue…

【LeetCode热题100】--1.两数之和

1.两数之和 方法一&#xff1a;最直观的方法就是暴力破解&#xff0c;就是枚举数组中的每一个数x&#xff0c;寻找数组中是否存在target-x class Solution {public int[] twoSum(int[] nums, int target) {int n nums.length;for(int i0;i<n;i){for(int j i1 ;j<n;j){…

基于SpringBoot的旅游系统

基于SpringBootVue的旅游系统、前后端分离 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringBoot、Vue、Mybaits Plus、ELementUI工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 【主要功能】 角色&#xff1a;管理员、用户 用户&#xff1a;浏览旅游…