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 := <-qpsChan
       a := dao.SelectXX(ctx, db, xxx)
       // TODO
       
       b := dao.SelectXXX(ctx, db, xxx)
       // TODO
       redis.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]Stmt
    PreparedSQL []string
    Mux         *sync.RWMutex
    ConnPool
}

看图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缓存

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式:简单工厂模式、工厂方法模式、抽象工厂模式

简单工厂模式、工厂方法模式、抽象工厂模式 1. 为什么需要工厂模式&#xff1f;2. 简单工厂模式2.1. 定义2.2. 代码实现2.3. 优点2.4. 缺点2.5. 适用场景 3. 工厂方法模式3.1. 有了简单工厂模式为什么还需要有工厂方法模式&#xff1f;3.2. 定义3.3. 代码实现3.4. 主要优点3.5.…

Python怎么修改进程名称

目录 一、进程名称的概念 二、Python修改进程名称的方法 三、代码示例与使用说明 四、注意事项 五、适用场景 六、总结 Python是一种强大的编程语言&#xff0c;广泛应用于各种应用程序的开发。在Python中&#xff0c;修改进程名称可以通过多种方式实现。下面我们将深入探…

红队打靶练习:SAR: 1

目录 信息收集 1、arp 2、netdiscover 3、nmap 4、nikto 5、whatweb 小结 目录探测 1、gobuster 2、dirsearch WEB CMS 1、cms漏洞探索 2、RCE漏洞利用 提权 get user.txt 本地提权 信息收集 1、arp ┌──(root㉿ru)-[~/kali] └─# arp-scan -l Interface:…

【Linux C | 文件操作】获取文件元数据的几个函数 | stat、fstat、lstat

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…

吸烟者问题-第三十一天

目录 问题描述 解决问题 是否需要设置一个专门的互斥信号量&#xff1f; 结论 问题描述 该题属于”生产者-消费者”问题&#xff0c;更详细的说应该是“可生产多种产品的单生产者-多消费者” 解决问题 1、 关系分析&#xff1a;找出题目中描述的各个进程&#xff0c;分析…

(一)输出输入

文章目录 输出printf输出格式控制常见的几种格式输出小数输出很奇特指定位数对齐方式 代码现象 输入scanf输入格式控制输入坑代码现象 %c 格式控制的坑混合类型输入问题 输出printf 输出格式控制 如&#xff1a;%(占位符/格式字符) printf(“a%d”,a); 常见的几种格式输出 …

海外网红营销:母婴品牌提升影响力和市场份额的绝佳途径

随着全球化的加速和社交媒体的普及&#xff0c;越来越多的母婴品牌开始寻求海外市场拓展。在这个过程中&#xff0c;海外网红营销成为了品牌方们青睐的策略之一。本文Nox聚星将和大家探讨母婴品牌如何利用海外网红营销来提升品牌影响力并拓展市场份额。 母婴品牌的核心消费者是…

BIOS:计算机中的特洛伊木马

内容概述&#xff1a; 由于主板制造商在计算机启动时用来显示品牌徽标的图像分析组件相关的问题&#xff0c;多个安全漏洞&#xff08;统称为 LogoFAIL&#xff09;允许攻击者干扰计算机设备的启动过程并安装 bootkit。x86 和 ARM 设备都面临风险。主板固件供应链安全公司 Bin…

02--数据定义语言DDL

1、数据定义语言DDL 1.1 操作数据库-DDL 创建数据库 create database 数据库名称; 创建数据库&#xff0c;并指定字符集 create database 数据库名称 character set 字符集名; 查询所有数据库的名称 show databases; 查询某个数据库的字符集:查询某个数据库的创建语句及字…

【Matlab】ELM极限学习机时序预测算法(附代码)

资源下载&#xff1a; https://download.csdn.net/download/vvoennvv/88681649 一&#xff0c;概述 ELM&#xff08;Extreme Learning Machine&#xff09;是一种单层前馈神经网络结构&#xff0c;与传统神经网络不同的是&#xff0c;ELM的隐层神经元权重以及偏置都是随机产生的…

tecplot360 提取某一点随时加变化的参数

tecplot360 提取某一点随时加变化的参数 效果过程录制宏打开所有数据&#xff08;都进来所有的data数据&#xff09; 效果 如下&#xff0c;红点处随时间变化的温度曲线 过程 简单理解就是将所有计算的data帧中固定点的参数取出来 所以先录制宏&#xff0c;然后应用宏自动取…

大模型时代下AIGC新浪潮

大模型时代下AIGC新浪潮 文章目录 大模型时代下AIGC新浪潮1. **相关概念**2. **迎接大模型时代**3. **ChatGPT引爆AIGC产业**4. **从产业链宏观看AIGC**1. **上游&#xff1a;基础层**2. **中游&#xff1a;技术层/模型层**1. **模型层介绍**2. **预训练大模型分类与介绍** 3. …

品优购实战案例

1. 开发工具 VScode 、Photoshop&#xff08;fw&#xff09;、主流浏览器&#xff08;以Chrome浏览器为主&#xff09; 2. 技术栈  利用 HTML5 CSS3 手动布局&#xff0c;可以大量使用 H5 新增标签和样式  采取结构与样式相分离&#xff0c;模块化开发  良好的代码规范有…

js常用事件演示

目录 JS事件的具体方法 窗口事件 表单事件 键盘事件 鼠标事件 知识小拓展 JS事件的具体方法 我们用到JavaScript的时候js的事件就显得特别重要了 事件名说明onsubmit当表单提交时触发该事件onclick鼠标单击事件ondblclick鼠标双击事件onblur元素失去焦点onfocus元素获得…

Eureka服务注册与发现中心

简介 Spring Cloud封装了Netflix 公司开发的Eureka模块来实现服务治理 在传统的RPC远程调用框架中&#xff0c;管理每个服务与服务之间依赖关系比较复杂&#xff0c;管理比较复杂&#xff0c;所以需要使用服务治理&#xff0c;管理服务于服务之间依赖关系&#xff0c;可以实现…

打印菱形和金字塔类型(总结)

首先&#xff0c;在之前的学习中&#xff0c;我们了解了菱形的打印&#xff0c;今天我们来对金字塔和菱形这类打印图形的问题&#xff0c;我们来做一个总结。 这个总结的来源是这今天做了一道题 这道题的答案如下 这个题做起来并不难&#xff0c;拓展到这类问题中&#xff0c;…

【驱动序列】简单聊聊开发驱动程序的缘由和驱动程序基本信息

大家好&#xff0c;我是全栈小5&#xff0c;欢迎来到《小5讲堂》&#xff0c;这是《驱动程序》专栏序列文章。 这是2024年第4篇文章&#xff0c;此篇文章是结合了C#知识点实践序列文章&#xff0c;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xf…

面向对象(类和对象,对象内存图,成员变量和局部变量,封装,构造方法)

1. 类和对象 1.1 类和对象的理解 客观存在的事物皆为对象 &#xff0c;所以我们也常常说万物皆对象。 类 类的理解 类是对现实生活中一类具有共同属性和行为的事物的抽象类是对象的数据类型&#xff0c;类是具有相同属性和行为的一组对象的集合简单理解&#xff1a;类就是对…

cJSON代码解读

1、背景 cJSON用了很久&#xff0c;但是对它一直不太了解。这次向添加对long long类型的支持&#xff0c;一直出问题。因为有以前添加两位小数float的经历&#xff0c;我觉得会很轻松&#xff0c;没想到翻车了。于是有了这边文档&#xff0c;阅读了部分博主对cJSON的解析&…

第二十七章 正则表达式

第二十七章 正则表达式 1.正则快速入门2.正则需求问题3.正则底层实现14.正则底层实现25.正则底层实现36.正则转义符7.正则字符匹配8.字符匹配案例19.字符匹配案例211.选择匹配符&#xff08;|&#xff09;12.正则限定符{n}{n,m}&#xff08;1个或者多个&#xff09;*(0个或者多…