当前位置: 首页 > article >正文

JDBC FetchSize不生效,批量变全量致OOM问题分析

背景

一个简单的基于 JDBC 采集数据库表的功能,当采集 Postgre SQL 某表,其数据量达到 500万左右的时候,程序一启动就将 JVM 堆内存「6G」干满了。

问题是程序中使用了游标的只前进配置,且设置了 fetchSize 属性:

queryStat = connection.prepareStatement(executeSql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
queryStat.setFetchSize(batchSize);

为什么这个批量拉取数据的配置不生效呢?本文记录这个问题的排查过程及优化方法。

导出堆内存

程序一启动,jmap -heap 查看堆内存,老年代直接干到 99.98 % ,这时的程序直接 Stop all the world ,僵了。
在这里插入图片描述
JVM 启动最大堆内存已经调整到 6G 了,还是撑不住。感觉 SQL 查询的时候一下子将表的全部结果都加载到内存了,前面配置的批量拉取设置根本没生效。导出堆内存文件,进行分析。

nohup jmap -F -dump:live,format=b,file=/home/dump-result.hprof 23055 &

堆内存太大了,只能走后台进程的方式导出,接近一个小时才导出了 dump 文件,5.8G,确实跟 JVM 最大内存一样了。

堆内存分析

使用 mat 打开这个文件,直接内存溢出了。然后修改 mat 的 JVM 参数到8G后,得到分析结果不对,才几十M,明显不符合。

有很多 unreachable object,重新修改 mat 配置,勾选 “keep unreachable objects”,同时修改展示单位为 MB:
在这里插入图片描述

删除上次分析的结果文件后,重新导入 dump 文件分析,得到分析结果:
在这里插入图片描述
点开 Leak Suspects 查看内存泄漏的地方,发现最大的对象4.5G ,是一个列表,列表元素类型是 org.postgresql.core.Tuple ,盲猜这个类就是 JDBC 封装的查询结果
在这里插入图片描述
而这个类的对象总数跟表记录总数基本一致:
在这里插入图片描述
少掉的那些,应该是 GC 努力回收过的,但是剩余量还是很大。

这基本验证了前面的猜测,批量查询实际上成了全量查询了。为了再次确认,调整代码,造一张同结构、但是数据总量6万左右的表,然后在 while(result.next()) 遍历的循环里面加上 sleep 10 分钟后启动程序,导出堆内存。

这次程序老年代内存没有撑满,导出内存分析,Tuple 这个查询结果类对象的个数,跟数据库表总记录数「58000」多了21,基本可以确定这个批量size 没有生效。
在这里插入图片描述

问题分析

为什么批量加载不生效呢?是数据库的问题?驱动的问题?

尝试的方法:

  1. ❌升级数据库驱动为最新版本,无效。
  2. ❌在 while(result.next()) 遍历过程中,直接打印一个字符串后 continue,休眠5秒,手动调用 GC。不做任何操作,且手动触发 GC,JVM 内存还是满了。
  3. ❌怀疑数据库有问题,确定测试环境版本和出问题的现场环境一致。
  4. ❌目标数据库是基于 OpenGauss 自研的数据库,难道不支持游标的批量获取数据?

搜到一篇文章 《Postgres查询结果集的获取方法及其优缺点》 ,里面提到了 PostgreSQL 数据库的批量获取游标结果集生效的四个条件:

  1. 连到数据库服务的连接必须是基于V3协议的,V3协议是7.4及更新版本PG才能支持的,并且是他们的默认协议;
  2. Connection必须是非自动提交模式.后端会在事务的结束的时候关闭游标,所以,在自动提交模式里,还没从游标里获取任何东西的时候,后端就已经把游标关闭了。「冷知识:Connection 默认是自动提交的。」
  3. Statement必须以ResultSet.TYPE_FORWARD_ONLY的类型来创建,该结果集类型是默认的,所以可以直接使用stmt = conn.createStatement()来创建(或者stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY)).因此基于游标的结果集是只能向前获取,不能向后或者跳跃获取的。「PS:PostgreSQL默认就是这个类型,所以这个不是关键。
  4. 查询sql语句必须是一个单一的语句,不能是由分好分隔的多个语句。这个在本应用中不存在。

之前没仔细注意第2点,找了三天实在没办法了。又打开这篇文章,仔细看了一下,发现了这个点。

检查代码确实没有设置自动提交参数,加上它,还原 JVM 参数为2G,然后测试500万条数据顺利采集完成,老年代堆只占2%。

复测验证:再去掉这行代码,回到原点,还是一启动就堆满了,确定这行代码就是关键。排查了三天的问题,就这么简单的一行代码就解决了吗?赶在周末之前干掉问题,真是太幸运了。

优化结果

继续优化,循环遍历数据总量到达一个值后,手动触发 GC并休眠1秒:

// 手动触发GC,且休眠等待
if (count == maxFetchSize) {logger.info("Reach max batch size {}, sleep 1s to gc", maxFetchSize);count = 0;// 手动触发 GCRuntime.getRuntime().gc();// 等待GC完成Thread.sleep(1000);
}

将优化后的结果,加上 sleep 10分钟后,导出堆栈分析,发现这次 Tuple 类的个数就是 setFetchSize=2000,还多了21个。
在这里插入图片描述
跟上面那个一样,数据总量+21,说明额外还有 21 个对象,为查询操作提供了不为人知的功能。总归来说,只有加上这句话 connection.setAutoCommit(false); 才生效,才是真正的批量查询数据。

启示录

一开始就检索到了 《Postgres查询结果集的获取方法及其优缺点》 这篇文章,里面提到了 PostgreSQL 数据库的批量获取游标结果集生效的方法,但是忽略了重要的那个条件。

循环处理数据时,达到一个值后,手动触发 GC 还是有效的,可以让整个采集过程中老年代内存占用情况稳定在 2% 左右;如果去掉 GC 的话,内存会缓慢升至 10% 左右,但是已经不会再僵死了。

这个 JDBC 的批量查询不生效问题,前年冬天采集 Doris 的时候也发现了,只是后来没有细究。这次又碰到了,不知掉 Doris 能不能用这个配置解决呢?或者说 Doris 数据库支不支持批量查询呢?

相关文章:

JDBC FetchSize不生效,批量变全量致OOM问题分析

背景 一个简单的基于 JDBC 采集数据库表的功能,当采集 Postgre SQL 某表,其数据量达到 500万左右的时候,程序一启动就将 JVM 堆内存「6G」干满了。 问题是程序中使用了游标的只前进配置,且设置了 fetchSize 属性: q…...

docker - compose up - d`命令解释,重复运行会覆盖原有容器吗

docker - compose up - d`命令解释,重复运行会覆盖原有容器吗 docker - compose up - d 是一个用于管理 Docker 容器的命令,具体含义如下: 命令含义: up:用于创建、启动并运行容器,会根据 docker - compose.yml 文件中定义的服务配置来操作。-d:表示以“分离模式”(det…...

Python 装饰器(Decorators)

什么是装饰器? 装饰器(Decorator)本质上是一个 修改其他函数功能的函数。它的核心思想是:不修改原函数代码,动态添加新功能。比如: 记录函数执行时间 检查用户权限 缓存计算结果 自动重试失败操作 理解…...

A2 最佳学习方法

记录自己想法的最好理由是发现自己的想法,并将其组织成可传播的形式 (The best reason for recording what one thinks is to discover what one thinks and to organize it in transmittable form.) Prof Ackoff 经验之谈: 做培训或者写文章&#xff…...

蓝桥杯省模拟赛 阶乘求值

问题描述 给定 n,求 n! 除以 1000000007的余数。 其中 n! 表示 n 的阶乘,值为从 1 连乘到 n 的积,即 n!123…n。 输入格式 输入一行包含一个整数 n。 输出格式 输出一行,包含一个整数,表示答案。 样例输入 3样…...

MYTOOL-记事本

一、前言 目录 1.原型设计 2.程序实现 3.最终界面说明 二、环境 windows10 每个软件工具前期会设计大概的原型,我设计的原型工具使用Axure RP9,很不错的一个设计工具 三、正文 1.原型设计 2.程序实现 3.最终界面说明 四、结语...

Golang使用 ip2region 查询IP的地区信息

利用 ip2region 进行 IP 地址定位 import ("fmt""log""github.com/lionsoul2014/ip2region/binding/golang/xdb" )func main() {ip : "213.118.179.98"dbPath : ".\\cmd\\ip\\ip2region.xdb"// 1、初始化查询器//searcher,…...

StarRocks 中 CURRENT_TIMESTAMP 和 CURRENT_TIME 分区过滤问题

背景 本文基于Starrocks 3.3.5 最近在进行Starrocks 跑数据的时候,发现了一个SQL 扫描了所有分区的数据,简化后的SQL如下: select date_created from tableA where date_createddate_format(current_time(), %Y-%m-%d %H:%i:%S) limit 20其…...

OMI(operating mode indication)

OMI(operating mode indication,操作模式指示)是11ax引入的用以交互形式分配兼容性以及信道带宽的协商。可以降终端活跃时间的耗电量. 802.11ax终端使用802.11数据使用OM控制字段(OM Control Subfield,其通常位于数据或者管理帧中),其用来指示改变AP的发送或者接收模式。8…...

4、网工软考—VLAN配置—hybird配置

1、实验环境搭建: 2、实验过程 SW1: 先创建vlan2和vlan3 [Huawei-Ethernet0/0/2]port link-type hybrid //hybird端口 [Huawei-Ethernet0/0/2]port hybrid pvid vlan 2 [Huawei-Ethernet0/0/2]port hybrid untagged vlan 10 //撕掉vlan10的标签 …...

Chrome 开发环境快速屏蔽 CORS 跨域限制!

Chrome 开发环境快速屏蔽 CORS 跨域限制【详细教程】 ❓ 为什么需要临时屏蔽 CORS? 在前后端开发过程中,我们经常会遇到 跨域请求被浏览器拦截 的问题。例如,你在 http://localhost:3000 调用 https://api.example.com 时,可能会…...

第 8 章:使用更好的库_《C++性能优化指南》_notes

使用更好的库 第八章核心知识点解析编译与测试建议总结优化原则重点内容:第一部分:多选题(10题)第二部分:设计题答案与解析多选题答案:设计题答案示例(部分): 测试用例设…...

基于深度学习的图像超分辨率技术研究与实现

一、引言 在数字图像处理领域,图像超分辨率技术一直是一个备受关注的热点话题。随着人们对图像质量要求的不断提高,如何将低分辨率图像提升到高分辨率,同时保持图像的细节和清晰度,成为了一个极具挑战性的问题。传统的图像超分辨率…...

ubuntu22.04 ROS2humble 路径文件

ROS2humble 路径文件 /opt/ros/humble/include/opt/ros/humble/lib/opt/ros/humble/share 下载ros2之后会有下面的文件,在/opt/ros/humble下 /opt/ros/humble/include C/C 头文件(.h, .hpp) /opt/ros/humble/lib 作用: 存放 编译生成的二…...

OpenCV 图形API(或称G-API)

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 引言 OpenCV 图形API(或称G-API)是一个新的OpenCV模块,旨在使常规图像处理更快且更便携。通过引入一种新的基于图的执行…...

数据设计(范式、步骤)

文章目录 数据设计1.数据库设计的三大范式2、数据库设计的具体步骤 数据设计 1.数据库设计的三大范式 关系型数据库的三大范式,指导如何设计一个关系型数据库。 1NF: 关系表的每个字段,都应该是不可再分的,——保证原子性。 字…...

Linux命令大全:从入门到高效运维

适合人群:Linux新手 | 运维工程师 | 开发者 目录 一、Linux常用命令(每天必用) 1. 文件与目录操作 2. 文件内容查看与编辑 二、次常用命令(按需使用) 1. 系统管理与监控 2. 网络与通信 3. 权限与用户管理 三、…...

系统与网络安全------网络应用基础(3)

资料整理于网络资料、书本资料、AI,仅供个人学习参考。 路由器 路由器认识路由器工作原理基本配置直连路由远程管理路由器远程连接测试 路由器 认识路由器 负责在不同网络之间转发数据的设备 路由器决定到达目标的路径 路由器也为直连网络的主机充当”网关“角色…...

常用的测试用例

登录、添加、删除、查询模块是我们经常遇到的,这些模块的测试点该如何考虑 1)登录 ① 用户名和密码都符合要求(格式上的要求) ② 用户名和密码都不符合要求(格式上的要求) ③ 用户名符合要求,密码不符合要求(格式上的要求) ④ 密码符合要求&#xf…...

标准库中有uint32_t类型吗?

标准 C 库中有uint32_t类型。它定义在<stdint.h>头文件中&#xff0c;表示无符号 32 位整数类型。 #include <stdio.h> #include <stdint.h>int main() {uint32_t num 4294967295; // 32位无符号整数的最大值printf("The value of num is %u\n"…...

作业(7)

接口ip配置和区域划分&#xff1a; fw1&#xff1a; [fw1]interface GigabitEthernet 0/0/0 [fw1-GigabitEthernet0/0/0]service-manage all permit [fw1]firewall zone trust [fw1-zone-trust]add interface GigabitEthernet 1/0/0 [fw1]security-policy [fw1-policy-secu…...

kafka 报错消息太大解决方案 Broker: Message size too large

kafka-configs.sh --bootstrap-server localhost:9092 \ --alter --entity-type topics \ --entity-name sim_result_zy \ --add-config max.message.bytes10485880 学习营课程...

Burp Suite抓包实战:SQL注入漏洞挖掘

本文系统解析如何利用Burp Suite专业版开展SQL注入漏洞的定向挖掘&#xff0c;涵盖手动探测、自动化利用、WAF绕过等进阶技巧。通过电商、金融等行业的真实渗透案例&#xff0c;详解从流量拦截到漏洞利用的全链路方法论&#xff0c;实现单日最高挖掘23个高危注入点的实战成果。…...

open-cv的安装

python -m pip install numpy matplotlib opencv-python 【记得科学上网&#xff0c;不然太慢了】...

docker-compose自定义网络,解决docker-compose网段路由冲突

问题排查 先route一波查看一下路由表 容器路由19和堡垒机路由冲突 解决方案 更改docker网段更改docker生成容器的网段 > 基本操作 docker network ls &#xff1a;查看docker网络列表 docker network inspect <network id/name>&#xff1a;查看某个docker网络详情…...

数据库三级选择题(2)

C) 分布式数据库的事务管理包括恢复控制和并发控制&#xff0c;恢复控制一般采用的策略是基于两阶段提交协议 采用一定的计算方法定位数据的有 Ⅳ&#xff0e;散列&#xff08;哈希&#xff09;索引 下列提供逻辑独立性的是外模式/模式映像 UML所有活动有关判断的部分要用菱形表…...

【入门初级篇】报表基础操作与功能介绍

【入门初级篇】报表的基本操作与功能介绍 视频要点 &#xff08;1&#xff09;报表组件的创建 &#xff08;2&#xff09;指标组件的使用&#xff1a;一级、二级指标操作演示 &#xff08;3&#xff09;表格属性设置介绍 &#xff08;4&#xff09;图表属性设置介绍 &#xff0…...

GenBI 中如何引入 LLM 做意图路由,区分查数据还是闲聊

写在前面 生成式商业智能(Generative BI, GenBI)的魅力在于其能够理解用户的自然语言,并将复杂的数据查询和分析过程自动化。用户不再需要学习 SQL 或操作复杂的界面,只需像与同事交谈一样提出问题,就能获得数据洞察。然而,一个现实的挑战是:用户的输入并非总是明确的数…...

视频编码器的抉择:x264、x265、libaom、vvenc 对比测试实验

264、x265、libaom、vvenc 对比测试实验 测试机器配置&#xff1a;Apple M1 Pro -16G编码器版本&#xff08;选择自己编译&#xff09;&#xff1a;所有源码都是当前最新更新的状态&#xff0c;此外各类编码具体的编译过程可参考我的相关系列博客。 编码器GitHubx264git clon…...

【多媒体交互】Unity Kinect实现UI控件的点击

在Unity中&#xff0c;通过Kinect实现UI控件的点击功能&#xff0c;主要涉及手部追踪、坐标映射和手势检测三个核心环节。 实现步骤 初始化Kinect与关节追踪 使用KinectManager获取用户ID和手部关节点&#xff08;如JointType.HandLeft&#xff09;的坐标。 long userId _…...