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

news2025/2/23 23:42:29

一、背景

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

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

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

相关文章

Python序列之字典

系列文章目录 Python序列之列表Python序列之元组Python序列之字典&#xff08;本篇文章&#xff09;Python序列之集合 Python序列之字典 系列文章目录前言一、字典是什么&#xff1f;二、字典的操作1.创建&#xff08;1&#xff09;通过{}、dict()创建&#xff08;2&#xff0…

实验室(检验科)信息系统LIS源码,客户端:WPF+Windows Forms

lis系统源码&#xff0c;医学检验信息系统源码 LIS系统&#xff08;Laboratory Information System&#xff09;即实验室&#xff08;检验科&#xff09;信息系统&#xff0c;它将检验仪器付出的检验数据与相关信息接入计算机网络系统中&#xff0c;让患者、实验室、临床科室、…

进入IC行业的学习之路:建议和必读书籍推荐

近期有不少渴望进入IC行业的同学在后台给我留言&#xff0c;他们询问如何入门&#xff1f;需要学习哪些内容&#xff1f;推荐的入门必读书籍。 在这个行业已经有些年头了&#xff0c;多多少少有一些经验之谈&#xff0c;今天在这里&#xff0c;我将以我的经验和专业知识为基础…

软件测试之自动化测试的四个阶段

第一阶段&#xff1a;API自动化 之前的想法是&#xff1a;通过API创建数据&#xff0c;访问数据&#xff0c;进行数据操作&#xff0c;存储数据库&#xff0c;通过模拟前端的操作来想象API的访问流程。 然后&#xff0c;验证数据库是否存储正确。后来发现该想法流程就是错误的…

IO作业2.0

思维导图 1> 使用fread、fwrite完成两个文件的拷贝 #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {if(argc ! 3) //判断外部参数 {printf("The terminal format is incorrect\n");r…

刚来实习就跑路,可行么?

最近 编程导航 的一位鱼友问了个让我血压升高的问题&#xff1a; 鱼友提问 鱼皮你好&#xff0c;我投了两周简历&#xff0c;然后昨天面了一个小厂的远程实习并且拿到了 offer&#xff0c;我要不要试试呢&#xff1f; 我在顾虑比如我如果在远程实习期间找到一个中厂或者大厂…

GPT-4在概念推理任务表现不如人类,还需继续学习提高!

圣达菲研究所的科研人员对 GPT-4在推理和抽象能力方面与人类的差距进行了定量研究。他们使用 ConceptARC 基准测试评估了 GPT-4在文本和多模态方面的表现&#xff0c;并发现 GPT-4仍然与人类存在较大差距。对于 GPT-4的抽象推理能力&#xff0c;研究人员发现&#xff0c;无论是…

浏览器---善用的一些调试技巧

https://www.cnblogs.com/dasusu/p/17932742.html

初识Linux下进程

&#x1f30e;初识进程 初识进程 简单认识一下进程 如何管理进程 进程属性信息 内核运行队列 查看进程 通过系统调用获取进程标识符       父子进程       查看运行中的进程 总结 前言&#xff1a; 我们在电脑上点开的一个个应用&#xff0c;其实就是一个个进程&am…

计算机网络(7):网络安全

网络安全问题 计算机网络上的通信面临以下的四种威胁: (1)截获(interception)攻击者从网络上窃听他人的通信内容。 (2)中断(interruption)攻击者有意中断他人在网络上的通信。 (3)篡改(modification)攻击者故意篡改网络上传送的报文。 (4)伪造(fabrication)攻击者伪造信息在网…

DrGraph原理示教 - OpenCV 4 功能 - 阈值

普通阈值 OpenCV中的阈值用于相对于提供的阈值分配像素值。在阈值处理中&#xff0c;将每个像素值与阈值进行比较&#xff0c;如果像素值小于阈值则设置为0&#xff0c;否则设置为最大值&#xff08;一般为255&#xff09;。 在OpenCV中&#xff0c;有多种阈值类型可供选择&am…

融资项目——异常处理

当前端请求后端服务的时候&#xff0c;如果后端出现bug。会返回给前端通用的500异常结果。 但是在项目开发中&#xff0c;我们一般会定义一个统一结果类R用于返回结果数据&#xff0c;所以我们希望将错误也用统一结果类对象返回给前端。 1. 同一异常处理器。 面对一般的错误&…

二维码地址门牌系统技术服务:让您的生活更便捷,一码通行,安全无忧

文章目录 前言一、融合二维码技术与门牌的便捷服务二、手机开门便捷功能三、智能化安全保障四、智能化、便捷化的新型技术 前言 在数字化时代&#xff0c;二维码门牌系统技术应运而生&#xff0c;为了满足人们对安全、便捷生活的需求。这项技术将二维码与门牌结合&#xff0c;…

关于各种浏览器或操作系统深色模式的设置,看这篇文章就够了

无论你是在家里设立办公室,还是带着笔记本电脑去当地的咖啡馆,或者在格子间度过一天,都可以通过在网络浏览器中启用深色模式来让你的眼睛休息一下。 深色模式不会改变你访问的页面的颜色(这是由网站开发者设置的);它将使工具栏和“设置”页面更容易查看,尤其是在冬季较…

centos7通过systemctl启动springboot服务代替java -jar方式启动

背景&#xff1a;以前启动jar包都是通过java -jar&#xff0c;后面了解到一种注册服务式启动jar包的方式&#xff0c;所以做个记录。 注意&#xff1a;我在写该篇博客的时候&#xff0c;给脚本代码都加了#注释来解释该行脚本命令的意思&#xff0c;所以可能出现复制该篇博客脚本…

gookit/color - Go语言命令行色彩使用库教程

gookit/color - Go语言命令行色彩使用库教程 1.安装2.基础颜色(16-color)3.256色彩/RGB风格 1.安装 go get github.com/gookit/color2.基础颜色(16-color) 提供通用的API方法&#xff1a;Print Printf Println Sprint Sprintf 1、例如&#xff1a; color.Yellow.Println(&q…

Elasticsearch:使用 ELSER v2 文本扩展进行语义搜索

Elastic 提供了一个强大的 ELSER 供我们进行语义搜索。ELSER 是一种稀疏向量的搜索方法。我们无需对它做任何的微调及训练。它是一种 out-of-domain 的模型。目前它仅对英文进行支持。希望将来它能对其它的语言支持的更好。更多关于 ELSER 的知识&#xff0c;请参阅文章 “Elas…

关键字:new关键字

在 Java 中&#xff0c;new关键字用于创建对象实例。它是对象创建的语法糖&#xff0c;用于分配内存空间并调用构造函数来初始化对象。 以下是new关键字的基本语法&#xff1a; 在上述语法中&#xff0c;ObjectType是要创建对象的类名&#xff0c;objectName是对象的引用变量…

Android textview展示富文本内容

今天实现的内容&#xff0c;就是上图的效果&#xff0c;通过Span方式展示图片&#xff0c;需要支持文字颜色改变、加粗。支持style\"color:green; font-weight:bold;\"展示。尤其style标签中的font-size、font-weight是在原生中不被支持的。 所以我们今天需要使用自…

听GPT 讲Rust源代码--compiler(2)

File: rust/compiler/rustc_codegen_cranelift/build_system/prepare.rs 在Rust源代码中&#xff0c;rust/compiler/rustc_codegen_cranelift/build_system/prepare.rs文件的作用是为Cranelift代码生成器构建系统准备依赖项。 具体来说&#xff0c;该文件的主要目标是处理Crane…