mybatis流式游标查询-导出DB大数据量查询OOM问题

问题场景

Mysql数据处理类型分以下三种

com.mysql.cj.protocol.a.result.ResultsetRowsStatic:普通查询,将结果集一次性全部拉取到内存

com.mysql.cj.protocol.a.result.ResultsetRowsCursor:游标查询,将结果集分批拉取到内存,按照fetchSize大小拉取,会占用当前连接直到连接关闭。在mysql那边会建立一个临时表写入磁盘(查询结束后由mysql回收处理),会导致mysql server磁盘io飙升。

com.mysql.cj.protocol.a.result.ResultsetRowsStreaming:流式查询,将结果集一条一条的拉取进内存,比较依赖网络,可能会造成网络阻塞。占用当前mysql连接。

 

所以在普通查询大数据量时如果JVM内存不够用会出现OOM异常。如下测试方案

数据量20w,一条数据大概2K

虚拟机参数 -Xmx256m -Xms256m

1)普通查询,大概接近200MGC释放

(2)流式查询,不会出现内存溢出

3)游标查询,不会出现内存溢出

执行原理分析

JDBC MySQL 服务端的交互是通过 Socket 完成的,完整请求链路

JDBC 客户端 -> 客户端 Socket -> MySQL -> 检索数据返回 -> MySQL 内核 Socket 缓冲区 -> 网络 -> 客户端 Socket Buffer -> JDBC 客户端

 

普通查询的方式在查询大数据量时,所在 JVM 可能会凉凉,原因如下:

MySQL Server 会将检索出的 SQL 结果集通过输出流写入到内核对应的 Socket Buffer

内核缓冲区通过 JDBC 发起的 TCP 链路进行回传数据,此时数据会先进入 JDBC 客户端所在内核缓冲区

JDBC 发起 SQL 操作后,程序会被阻塞在输入流的 read 操作上,当缓冲区有数据时,程序会被唤醒进而将缓冲区数据读取到 JVM 内存中

MySQL Server 会不断发送数据,JDBC 不断读取缓冲区数据到 Java 内存中,虽然此时数据已到 JDBC 所在程序本地,但是 JDBC 还没有对 execute 方法调用处进行响应,因为需要等到对应数据读取完毕才会返回

弊端就显而易见了,如果查询数据量过大,会不断经历 GC,然后就是内存溢出

 

普通查询等待时间与游标查询等待时间原理上是不一致的,前者是一致在读取网络缓冲区的数据,没有响应到业务层面;后者是 MySQL 在准备临时数据空间,没有响应到 JDBC

游标查询消费完 fetchSize 行数据,就需要发起请求到服务端请求

 

流式查询

当客户端与 MySQL Server 端建立起连接并且交互查询时,MySQL Server 会通过输出流将 SQL 结果集返回输出,也就是 向本地的内核对应的 Socket Buffer 中写入数据,然后将内核中的数据通过 TCP 链路回传数据到 JDBC 对应的服务器内核缓冲区

JDBC 通过输入流 read 方法去读取内核缓冲区数据,因为开启了流式读取,每次业务程序接收到的数据只有一条

MySQL 服务端会向 JDBC 代表的客户端内核源源不断的输送数据,直到客户端请求 Socket 缓冲区满,这时的 MySQL 服务端会阻塞

对于 JDBC 客户端而言,数据每次读取都是从本机器的内核缓冲区,所以性能会更快一些,一般情况不必担心本机内核无数据消费(除非 MySQL 服务端传递来的数据,在客户端不做任何业务逻辑,拿到数据直接放弃,会发生客户端消费比服务端超前的情况)

代码实现使用

依赖

<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.1</version>
</dependency>
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.0</version>
</dependency>

流式查询

Mapper接口返回值为void,依靠ResultHandler进行结果处理

void queryAllTest(ResultHandler<TradeOrderDO> resultHandler);

xml定义-----fetchSizeInteger.MIN_VALUE  ,这个属性是JDBC每次去数据页获取的条数,设置最大就是由JDBC智能发挥。

<select id="queryAllTest" resultMap="TradeOrderOutput" resultSetType="FORWARD_ONLY" fetchSize="-2147483648">select * from eppc_db.t_trade_order</select>

以上也可以用注解实现,如下

// @ResultType(TradeOrderDO.class)// @Select("select * from eppc_db.t_trade_order order by Fpkid desc")//@Options(resultSetType = ResultSetType.FORWARD_ONLY,fetchSize = Integer.MIN_VALUE)void queryAllTest(ResultHandler<TradeOrderDO> resultHandler);

Service

@Overridepublic List<TradeOrderDO> queryList() {List<TradeOrderDO> tradeOrderDOList = new ArrayList<>();List<String> cardIds = new ArrayList<>();AtomicInteger i = new AtomicInteger(0);tradeinfoDAO.queryAllTest(resultHandler ->{TradeOrderDO resultObject = resultHandler.getResultObject();if (i.get() % 100000 == 0){//此处做业务处理System.out.println(resultObject.getPkid());
// tradeOrderDOList.add(resultHandler.getResultObject());}i.getAndIncrement();});return tradeOrderDOList;}

游标查询 2种方式

方式1

Mapper接口-----这种是在mapper层直接定义返回游标封装信息

//@Options(resultSetType = ResultSetType.FORWARD_ONLY,fetchSize = Integer.MIN_VALUE)//@Select("select * from eppc_db.t_trade_order")// @ResultType(TradeOrderDO.class)Cursor<TradeOrderDO> getAllRecord();

方式2—需要在service层使用sqlSession调用

//@Options(resultSetType = ResultSetType.FORWARD_ONLY,fetchSize = Integer.MIN_VALUE)//@Select("select * from eppc_db.t_trade_order")// @ResultType(TradeOrderDO.class)
List<TradeOrderDO> getAllRecords();

Service层—需注意加上事务注解表示该service并不是在mapper结束时结束事务,而是等整个service结束才结束事务,不然会出现只能读取到第一段游标的结果集。

@Resource(name = "eppcSqlSessionFactory")
SqlSessionFactory sqlSessionFactory;
@Override
@Transactional(readOnly = true)
public List<TradeOrderDO> getAllRecord() {List<TradeOrderDO> tradeOrderDOList = new ArrayList<>();Cursor<TradeOrderDO> cursor = null;SqlSession sqlSession = null;try {cursor = tradeinfoDAO.getAllRecord();//方式1调用sqlSession = sqlSessionFactory.openSession();cursor = sqlSession.selectCursor(TradeinfoDAO.class.getName() + ".getAllRecords");//方式2调用int currentIndex = 0;Iterator<TradeOrderDO> iterator = cursor.iterator();while (iterator.hasNext()){System.out.println(iterator.next()+""+currentIndex);/*if (currentIndex % 100000 == 0){//一次业务处理System.out.println("先写入一部分数据"+iterator.next()+currentIndex);}*/currentIndex ++;}} catch (Exception e) {e.printStackTrace();} finally {if (null != cursor) {try {cursor.close();} catch (Exception e) {log.error(e.getMessage(), e);}}if (null != sqlSession) {try {sqlSession.close();} catch (Exception e) {log.error(e.getMessage(), e);}return tradeOrderDOList;}}

使用总结

当遇到大数据量查询时确实可以使用mybatis的游标或者游式查询,Mysql底层也支持。但这只是减缓了数据库服务器的读与传输的压力。到业务层面还是需要根据具体业务场景去分批处理,比如一条查300w数据,游式查询能支持,但也不能一起性放入javalist中,内存不够还是会溢出。这时可能就需要写一些条件一次处理多少数据,所以本质来说就是数据不一次性存储,但总有地方要把这些数据存着。不给JVM内存,那就会牺牲网络或者服务器的其它属性。

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

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

相关文章

【Windows11】cmd下运行python弹出windows应用商店解决方案

【Windows11 】cmd下运行python弹出windows应用商店解决方案 大家好 我是寸铁&#x1f44a; 总结了一篇【Windows11 】cmd下运行python弹出windows应用商店解决方案✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 前言 今天在安装python时&#xff0c;在命令行窗口输入如下命令&a…

tomcat中的web项目配置指引

文章目录 目录结构I server.xml 配置文件1.1 Host标签1.2 contex标签1.3 server.xml 的端口配置1.4 appBase和docBase的区别1.5 Engine标签1.6 Connector标签II Tomcat应用的配置2.1 配置虚拟路径2.2 配置连接数2.3 使用线程池2.4 配置内存大小III 预备知识

【面试经典150 | 动态规划】不同路径 II

文章目录 写在前面Tag题目1方法一&#xff1a;动态规划方法二&#xff1a;空间优化 题目2方法一&#xff1a;动态规划空间优化 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主…

启动mysql

删除C:\Program Files (x86)\MySQL\MySQL Server 5.7这个路径下的data文件夹&#xff0c;这个很难删除&#xff0c;因为一开机&#xff0c;mysql的某些服务就启动了&#xff0c;每次重新启动mysql之前&#xff0c;都要删除这个文件夹 因为这个文件夹在后端执行一些我们看不到的…

2024.3.31学习记录————树莓派4B学习一:环境配置

2024.3.31学习记录————树莓派4B学习一&#xff1a;环境配置 准备跳槽&#xff0c;重新选择方向以嵌入式为目标进行学习&#xff0c;因没有硬件基础&#xff0c;先选择了树莓派&#xff0c;准备通过树莓派开发板对驱动初步熟悉后再开始STM32和51单片机开发。 硬件配置 硬…

[技术闲聊]我对电路设计的理解(五)-如何获取datasheet

一、序言 电路设计的前提是明确设计规格&#xff0c;并转化为具体功能&#xff0c;依据功能确定芯片选型范围。 元器件选型的前提是了解每一类芯片的功能&#xff0c;界定该芯片是否合适。 了解的前提是有datasheet文件可以看。 二、获取datasheet的途径有三类 一类&#x…

MySQL-逻辑架构:逻辑架构分析、SQL执行流程、数据库缓冲池

逻辑架构 1. 逻辑架构剖析 1.1 第1层&#xff1a;连接层 系统&#xff08;客户端&#xff09;访问MySQL服务器前&#xff0c;做的第一件事就是建立TCP连接。 经过三次握手建立连接成功后&#xff0c;MySQL服务器对TCP传输过来的账号密码做身份认证、权限获取。 用户名或密码…

numpy 向量的运算法则公式

import numpy as np #构建向量数组 anp.array([-1,2]) bnp.array([3,-1]) #加法 a_bab #数乘 a2a*2 b3b*(-3) #减法 b_aa-b #print(a_b,a2,b3,b_a,a,b)print(-13,2-1,a_b)print(-1-3,2-(-1),b_a)print((-1*2),(2*2),a2)print((3*(-3)),(-1*(-3)),b3) 这段代码主要是使用NumPy库…

C语言------冒泡法排序

一.前情提要 1.介绍 冒泡法排序法&#xff1a; 1)冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;它重复地遍历要排序的列表&#xff0c;一次比较相邻的两个元素&#xff0c;并且如果它们的顺序错误就将它们交换过来。重复这个过程直到没有需…

DeepWalk论文翻译

DeepWalk论文翻译 DeepWalk: Online Learning of Social Representations DeepWalk&#xff1a;社会表征的在线学习 ABSTRACT 我们提出了 DeepWalk&#xff0c;一种学习网络中顶点潜在表示的新方法。这些潜在表示在连续向量空间中对社会关系进行编码&#xff0c;很容易被统…

VRRP虚拟路由器冗余协议

vrrp是为了解决单点故障问题 将几台路由器联合成一台虚拟的路由器&#xff0c;保证通信的可靠性 协议小说&#xff1a; 协议不是在固定的哪一个层&#xff0c;是基于哪一层工作&#xff0c;比如说ospf是基于三层工作的 VRRP是基于三层工作的&#xff0c;就在前面会封装一个ip…

JAVAEE——多线程进阶,锁策略

文章目录 锁策略乐观锁和悲观锁乐观锁悲观锁两者的比较 读写锁重量级锁和轻量级锁重量级锁轻量级锁 自旋锁公平锁和非公平锁公平锁非公平锁 可重入锁和不可重入锁可重入锁不可重入锁 锁策略 乐观锁和悲观锁 乐观锁 什么是乐观锁呢&#xff1f;我们可以认为乐观锁比较自信&am…

网络原理 - HTTP / HTTPS(4)——构造http请求

目录 一、postman 的下载安装以及简单介绍 1、下载安装 2、postman的介绍 二、通过 Java socket 构造 HTTP 请求 构造http请求的方式有两种&#xff1a;&#xff08;1&#xff09;通过代码构造&#xff08;有一点难度&#xff09; &#xff08;2&#xff09;通过第三…

Anaconda/Python快速安装jieba 【win/mac】

一、直接上命令 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple jieba 我是在PyCharm里面的终端输进去。 之后就很快速的看到成功的下图。 二、官网 官网下载的速度太慢了——这是官网地址https://pypi.org/project/jieba/#files 点进去之后点击下载&#xff0c…

【卷积神经网络进展】

打基础日常记录 CNN基础知识1. 感知机2. DNN 深度神经网络&#xff08;全连接神经网络&#xff09;DNN 与感知机的区别DNN特点&#xff0c;全连接神经网络DNN前向传播和反向传播 3. CNN结构【提取特征分类】4. CNN应用于文本 CNN基础知识 1. 感知机 单层感知机就是一个二分类…

Mysql不同条件设置相同的值(使用子查询)

WHEN type1 THEN payable_price WHEN type2 THEN payable_price ELSE MAX(payable_price) type1 表示订单维度&#xff0c;type2表示商品维度&#xff0c;需要无论type值为多少都取type1时的payable_price值 解决方案&#xff1a; 使用SQL子查询; SELECT CASE WHEN type …

利用AI结合无极低码(免费版)快速实现接口开发教程,会sql即可,不需要编写编译代码

无极低码无代码写服务+AI实践 本次演示最简单的单表无代码增删改查发布服务功能,更复杂的多表操作,安全验证,多接口调用,自自动生成接口服务,生成二开代码,生成调用接口测试,一键生成管理界面多条件检索、修改、删除、查看、通用公共接口调用、通用无限级字典调用等后续…

Java的Cookie和Session配合解决会话管理问题

目录 会话管理概述 为什么需要会话管理 会话管理实现的手段 Cookie Cookie概述 Cookie的使用 Cookie的时效性 Cookie的提交路径 Session HttpSession概述 HttpSession的使用 HttpSession时效性 cookie和session结合使用 会话管理概述 为什么需要会话管理 在Java…

【Java入门教程】第二十讲:集合和常见操作

集合框架&#xff08;Collection&#xff09;是 Java 编程语言中一个非常重要的组成部分&#xff0c;它为存储和操作数据提供了一套灵活而强大的API。 本文将详细介绍Java集合框架的基本概念、常用接口和实现类&#xff0c;并通过代码示例来加深理解&#xff0c;帮助大家更好地…

Vue中生成二维码,使用现有的二维码库qrcode

要在Vue中生成二维码&#xff0c;你可以使用现有的二维码库&#xff0c;如qrcode。首先&#xff0c;你需要安装这个库。在你的项目目录下&#xff0c;运行以下命令&#xff1a; bash 复制 npm install qrcode --save 然后&#xff0c;在你的Vue组件中&#xff0c;你可以这样使…