gorm.PrepareStmt模式使用不当问题查询

一、背景

xx服务内存持续上涨。内存占用10%以内,在QPS无明显变化的前提下,内存占用50%左右。

dump了一下heap内存,发现主要是 InitUserCacheRefresh 任务代码占用

正常来说,dao层查完数据库之后,对象应该会释放,最终被gc回收,但这里 InitUserCacheRefresh 代码里的对象长期持有引用,占用内存达400M+,感觉发生了内存泄露,所以排查下。

核心代码逻辑

该代码主要用于权限服务刷新用户权限缓存,在服务启动时会初始化50个协程通过 chan 等待用户权限刷新任务,刷新任务由 RefreshAllPermission/RefreshUserPermission 接口触发

// 使用伪码减少逻辑理解成本
func InitUserCacheRefresh() {ctx := context.Background()for i := 0; i < 50; i++ {go func() {// ...updateUserCache(ctx)}()}
}func updateUserCache(ctx context.Context) {db := client.GetDB(ctx)for {task := <-qpsChana := dao.SelectXX(ctx, db, xxx)// TODOb := dao.SelectXXX(ctx, db, xxx)// TODOredis.GetClient().Set(xxx, xxx)}
}

二、排查

一开始怀疑是 updateUserCache 方法内 db 变量在查询完结果后,有可能还持有结果引用,而 for 循环导致 db 变量一直无法回收,引用无法释放,导致内存泄露。

尝试本地复现:按照线上的逻辑写了个类似的代码尝试复现,但没有复现出来

给gorm提交oncall,咨询了下相关用法会不会导致数据引用无法释放,但也没结论

于是尝试在xx服务测试环境复现,部署服务后尝试调用几次 RefreshAllPermission 后dump内存,发现和线上基本一致,也持有 gorm 的很多对象(事情已经成功了百分之八十)。直接找到占用内存最大的对象 PreparedStmtDB,查看查询走到的逻辑(图1),prepare方法会优先在db.Stmts这个map中看存不存在对应query(SQL),如果存在就直接返回,如果不存在会创建一个新的放到这个map中。

在这里插入图片描述

在触发了几次 RefreshAllPermission 后,直接在图1断点处打上断点,发现db.Stmts有大量的SQL缓存
在这里插入图片描述

查看 PreparedStmtDB.Stmts 字段是一个 map,缓存SQL和对应的Stmt

type PreparedStmtDB struct {Stmts       map[string]StmtPreparedSQL []stringMux         *sync.RWMutexConnPool
}

看图2 感觉 PreparedStmtDB.Stmts 对象无限增长,没有清理策略,看 prepare_stmt.go 代码
只有在 close 的时候,以及err != nil的时候会清理

func (db *PreparedStmtDB) Close() {db.Mux.Lock()defer db.Mux.Unlock()for _, query := range db.PreparedSQL {if stmt, ok := db.Stmts[query]; ok {delete(db.Stmts, query)go stmt.Close()}}
}func (db *PreparedStmtDB) QueryContext(ctx context.Context, query string, args ...interface{}) (rows *sql.Rows, err error) {stmt, err := db.prepare(ctx, db.ConnPool, false, query)if err == nil {rows, err = stmt.QueryContext(ctx, args...)if err != nil {db.Mux.Lock()defer db.Mux.Unlock()go stmt.Close()delete(db.Stmts, query)}}return rows, err
}

所以感觉已经真相大白,只有在DB配置PrepareStmt为true情况会缓存SQL,尝试把DB的这个置为false,再次尝试,对应对象已经没了,调用多次后内存没有明显变化。详细的解决方案可以看下文结论中的解决方案

三、结论

根因

  1. xx服务DB配置开启了 PrepareStmt,也就是 PrepareStmt 配置为 true,gorm会缓存查询的SQL
  2. dao层使用的SQL没有使用预占符,而是通过 fmt.Sprintf 拼接查询,SQL中某个id或其他查询条件不一样就会导致gorm生成的SQL也不一样,gorm会将这些SQL都缓存下来,且没有容量上线和清理机制(使用map缓存),导致占用了大量内存。

解决方案

方式1

gorm 修复,缓存SQL的map改成LRU,设置容量,达到容量值时淘汰缓存的SQL。

方式2

xx服务更改DB配置,关闭PrepareStmt模式,将 PrepareStmt 配置改为 false。但PrepareStmt模式可以大幅提高SQL查询性能,建议在单独sql处使用。

方式3

xx服务的查询,动态SQL改成预占符

// 建议
Where(fmt.Sprintf("%v.role_status = ?", roleEntityTableName), 1)// 不建议
Where(fmt.Sprintf("%v.role_status = %d", roleEntityTableName, 1))

部署服务,再看断点处数据,缓存的SQL只有预占符的SQL,没有带id参数的,只有五条,不会占用大量内存

倾向选择方式3,也建议SQL使用预占符,而不是通过 fmt.Sprintf 拼接。另外也给gorm提交,反馈后续会修复,将缓存SQL的map改为LRU缓存

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

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

相关文章

边缘计算网关:重新定义物联网数据处理

随着物联网&#xff08;IoT&#xff09;设备的爆炸式增长&#xff0c;数据处理和分析的需求也在迅速增加。传统的数据处理方式&#xff0c;将所有数据传输到中心服务器进行处理&#xff0c;不仅增加了网络负担&#xff0c;还可能导致数据延迟和安全问题。因此&#xff0c;边缘计…

大数据机器学习GAN:生成对抗网络GAN全维度介绍与实战

文章目录 大数据机器学习GAN&#xff1a;生成对抗网络GAN全维度介绍与实战一、引言1.1 生成对抗网络简介1.2 应用领域概览1.3 GAN的重要性 二、理论基础2.1 生成对抗网络的工作原理2.1.1 生成器生成过程 2.1.2 判别器判别过程 2.1.3 训练过程训练代码示例 2.1.4 平衡与收敛 2.2…

SpringBoot 日志打印

一. 自定义打印日志 开发者自定义打印日志实现步骤: • 在程序中得到日志对象 • 使用日志对象的相关语法输出要打印的内容. 得到日志对象: //日志工厂需要将需要打印的类的类型传递进去,这样我们才知道日志的归属类,才能更方便的定位到文体类 private static Logger logger …

LaTeX写论文,公式后段落取消缩进方法:\noindent

在论文的段落中&#xff0c;需要插入一个公式&#xff0c;按道理公式后应该紧接着是段落的文本内容&#xff0c;但如果直接写的话&#xff0c;编译得到的PDF中呈现出来的却是开头缩进的样子 如果需要取消公式后面的段落缩进&#xff0c;可以使用命令 \noindent 该命令的作用…

git基础概念和常用命令(日常开发收藏备用)

目录 ### 常用命令 ### 远程仓库与克隆 ### 分支管理 ### 子模块&#xff08;Submodule&#xff09; ### 其他高级操作 ### 交互式暂存&#xff08;Interactive Staging&#xff09; ### cherry-pick ### rebase ### reflog与reset ### 子树合并&#xff08;Subtree …

深入理解 C# 中的字符串比较:String.CompareTo vs String.Equals

深入理解 C# 中的字符串比较&#xff1a;String.CompareTo vs String.Equals 在处理字符串时&#xff0c;了解如何正确比较它们对于编写清晰、有效和可靠的 C# 程序至关重要。本文将深入探讨 C# 中的两个常用字符串比较方法&#xff1a;String.CompareTo 和 String.Equals&…

代码质量评价及设计原则

1.评价代码质量的标准 1.1 可维护性 可维护性强的代码指的是: 在不去破坏原有的代码设计以及不引入新的BUG的前提下,能够快速的修改或者新增代码. 不易维护的代码指的是: 在添加或者修改一些功能逻辑的时候,存在极大的引入新的BUG的风险,并且需要花费的时间也很长. 代码可…

三、C语言中的分支与循环—while循环 (5)

本章分支结构的学习内容如下&#xff1a; 三、C语言中的分支与循环—if语句 (1) 三、C语言中的分支与循环—关系操作符 (2) 三、C语言中的分支与循环—条件操作符 与逻辑操作符(3) 三、C语言中的分支与循环—switch语句&#xff08;4&#xff09;分支结构 完 本章循环结…

ARCGIS PRO SDK 访问Geometry对象

一、Geometry常用对象 二、主要类 1、ReadOnlyPartCollection&#xff1a;Polyline 和 Polygon 使用的 ReadOnlySegmentCollection 部件的只读集合&#xff0c;属性成员&#xff1a;​ 名字描述Count获取 ICollection 中包含的元素数。TIEM获取位于指定索引处的元素。Spatial…

macbook安装mysql

参考这两篇文章&#xff1a; https://blog.csdn.net/zz00008888/article/details/109091478 https://blog.csdn.net/m0_67400973/article/details/126034807 其中&#xff0c;在配置文件这里&#xff0c;不太熟悉linux指令 cd /&#xff1a;返回根目录 sudo vim ~/.zshrc&am…

VSCode远程开发配置

目录 概要远程开发插件安装开始连接SSH无密码登录开发环境配置 概要 现在很多公司都是直接远程到服务器上写代码&#xff0c;使用远程开发&#xff0c;可以在与生产环境相同的环境中开发、测试和部署代码&#xff0c;减少因环境不同而导致的问题。本文将详细介绍如何通过VSCod…

SpringBoot 一个注解实现数据脱敏

什么是数据脱敏 数据脱敏是指对某些敏感信息&#xff0c;例如姓名、身份证号码、手机号、固定电话、银行卡号、邮箱等个人信息&#xff0c;通过脱敏算法进行数据变形&#xff0c;以保护敏感隐私数据。 数据脱敏通常涉及以下几种主要方法&#xff1a; 替换&#xff1a; 将原始…

如何使用Docker部署Swagger Editor结合内网穿透实现远程编辑API文档

文章目录 Swagger Editor本地接口文档公网远程访问1. 部署Swagger Editor2. Linux安装Cpolar3. 配置Swagger Editor公网地址4. 远程访问Swagger Editor5. 固定Swagger Editor公网地址 Swagger Editor本地接口文档公网远程访问 Swagger Editor是一个用于编写OpenAPI规范的开源编…

[python]matplotlib

整体图示 .ipynb 转换md时候图片不能通知携带&#xff0c;所有图片失效&#xff0c;不过直接运行代码可以执行 figure figure,axes与axis import matplotlib.pyplot as plt figplt.figure() fig2plt.subplots() fig3,axsplt.subplots(2,2) plt.show()<Figure size 640x480 …

windows2012 安装mysql5.7

windows2012 安装mysql5.7 1.安装1.解压文件夹2.把my文件拷入没有sql安装目录3.编辑my文件4.按照下方进行配置5.cmd进入bin目录6.出现丢失文件7.安装这个文件即可解决8.开始进行安装&#xff0c;输入mysqld install9.初始化mysql&#xff08;mysqld --initialize --console&…

Mybatis行为配置之Ⅳ—日志

专栏精选 引入Mybatis Mybatis的快速入门 Mybatis的增删改查扩展功能说明 mapper映射的参数和结果 Mybatis复杂类型的结果映射 Mybatis基于注解的结果映射 Mybatis枚举类型处理和类型处理器 再谈动态SQL Mybatis配置入门 Mybatis行为配置之Ⅰ—缓存 Mybatis行为配置…

Hive学习(13)lag和lead函数取偏移量

hive里面lag函数 在数据处理和分析中&#xff0c;窗口函数是一种重要的技术&#xff0c;用于在数据集中执行聚合和分析操作。Hive作为一种大数据处理框架&#xff0c;也提供了窗口函数的支持。在Hive中&#xff0c;Lag函数是一种常用的窗口函数&#xff0c;可以用于计算前一行…

项目记录:利用Redis实现缓存以提升查询效率

一、概述 当我们查询所有数据时&#xff0c;如果缓存中没有&#xff0c;则去数据库查询&#xff0c;如果有&#xff0c;直接查缓存的数据就行。注意定期更新缓存数据。 二、主体代码 private static final String ROOM_SCHEDULES_HASH "RoomSchedules";Overridepu…

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机的图像剪切(ROI)功能(C++)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK设置相机的图像剪切&#xff08;ROI&#xff09;功能&#xff08;C&#xff09; Baumer工业相机Baumer工业相机的图像剪切&#xff08;ROI&#xff09;功能的技术背景CameraExplorer如何使用图像剪切&#xff08;ROI&#xff09;功…

计算机网络——传输层(五)

前言&#xff1a; 最重要的网络层我们已经学习完了&#xff0c;下面让我们再往上一层&#xff0c;对网络层的上一层传输层进行一个学习与了解&#xff0c;学习网络层的基本概念和网络层中的TCP协议和UDP协议 目录 ​编辑一、传输层的概述&#xff1a; 1.传输层&#xff1a; …