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

@Override

public 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内存,那就会牺牲网络或者服务器的其它属性。

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

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

相关文章

【Python基础教程】5. 数

&#x1f388;个人主页&#xff1a;豌豆射手^ &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;python基础教程 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、…

文本处理语言awk基本语法

文章目录 运算符流程控制函数封装 awk语言初步 AWK 是一种强大的文本处理和数据解析工具&#xff0c;它支持丰富的运算符和流程控制语句。运算符方面&#xff0c;AWK 提供了基本的算术运算符&#xff08;, -, *, /, %, ^, **&#xff09;和赋值运算符&#xff08;, -, *, /, %…

【QT+QGIS跨平台编译】056:【pdal_json_schema+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

点击查看专栏目录 文章目录 一、pdal_json_schema介绍二、pdal下载三、文件分析四、pro文件五、编译实践一、pdal_json_schema介绍 pdal_json_schema 是与 PDAL(Point Data Abstraction Library)相关的 JSON 模式文件。PDAL 是一个用于处理和分析点云数据的开源库。JSON 模式…

常见寻找 SQL 注入漏洞方法总结

一、借助推理进行测试 识别 SOL 注入漏洞有一种简单的规则:通过发送意外数据来触发异常。该规则包括如下含义: 1. 识别 Web 应用上所有的数据输入。 2. 了解哪种类型的请求会触发异常。 3. 检测服务器响应中的异常。 二、通过参数判断 假设你正在访问一个网站&#xff0c…

NoSQL之Redis配置

文章目录 NoSQL之Redis配置一、关系数据库和非关系数据库1、关系型数据库2、非关系型数据库3、非关系型数据库产生背景4、关系型数据库和非关系型数据库的区别4.1 数据存储方式不同4.2 扩展方式不同4.3 对事务性的支持不同 5、总结5.1 关系型数据库5.2 非关系型数据库 二、Redi…

Spring-IoC 基于注解

基于xml方法见&#xff1a;http://t.csdnimg.cn/dir8j 注解是代码中的一种特殊标记&#xff0c;可以在编译、类加载和运行时被读取&#xff0c;执行相应的处理&#xff0c;简化 Spring的 XML配置。 格式&#xff1a;注解(属性1"属性值1",...) 可以加在类上…

大数据基础设施搭建 - Spark

文章目录 一、解压压缩包二、修改配置文件conf/spark-env.sh三、测试提交Spark任务四、Spark on Hive配置4.1 创建hive-site.xml&#xff08;spark/conf目录&#xff09;4.2 查看hive的hive-site.xml配置与3.1配置的是否一致4.3 测试SparkSQL4.3.1 启动SparkSQL客户端&#xff…

【JAVA】JAVA快速入门(长期维护)

下面是java的一些入门基础知识&#xff0c;有需要借鉴即可。 课程&#xff1a;B站黑马程序员&#xff0c;JAVA入门LINK 一、初识JAVA 1.java概述 概念&#xff1a;java是由sun公司研发&#xff0c;在2009年被oracle收购&#xff0c;祖师爷詹姆斯高斯林&#xff0c;是一种高级…

Copilot for Microsoft365使用心得

从去年3月份的发布到上周获得的体验名额&#xff0c;关注copilot已经超过了一年&#xff0c; 实际体验了一周觉得微软这款产品真的挺厉害的&#xff0c;至少在我认知里面确实可以减少很多的工作量&#xff0c;在此感谢陈老师公众号的体验卡的活动&#xff08;活动真实有效&…

101. 对称二叉树及同类题

101. 对称二叉树 力扣题目链接(opens new window) 给定一个二叉树&#xff0c;检查它是否是镜像对称的。 递归 /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNo…

【青龙脚本】星抖

脚本出处:Huaji 功能:完成日常任务 每天运行1次即可 变量名:yuanshen_xddj 手机登录软件后&#xff0c;抓包&#xff0c;搜索Authorization里面的参数 注意:每天12小时都要进软件领取金块&#xff0c;超过12小时就会停止产出 参数设置都在脚本注释里&#xff0c;懂的都懂&a…

Redis从入门到精通(五)Redis实战(二)商户查询缓存

↑↑↑请在文章头部下载测试项目原代码↑↑↑ 文章目录 前言4.2 商户查询缓存4.2.1 缓存介绍4.2.2 查询商户信息的传统做法4.2.2.1 接口文档4.2.2.2 代码实现4.2.2.3 功能测试 4.2.3 查询商户信息添加Redis缓存4.2.3.1 逻辑分析4.2.3.2 代码实现4.2.3.3 功能测试 4.2.3 数据一致…

传输层 --- UDP

目录 1. 传输层是什么呢&#xff1f; 2. 再谈端口号 2.1. 端口号是什么 2.2. 协议号是什么 2.3. 认识知名端口号 2.4. 端口号的相关问题 2.4.1. 一个进程可以绑定多个端口号吗&#xff1f; 2.4.2. 一个端口号可以被多个进程绑定吗&#xff1f; 2.4.3. 为什么不使用P…

向量数据库 | AI时代的航道灯塔

向量数据库 | AI时代的航道灯塔 什么是向量检索服务拍照搜商品 你使用过向量数据库吗&#xff1f;使用体验&#xff1f;为什么向量数据库能借由大模型引起众多关注向量数据库在当前AI热潮中是昙花一现&#xff0c;还是未来AI时代的航道灯塔&#xff1f; 今天的话题主要是讨论向…

python-基础篇-字符串、列表、元祖、字典-列表

文章目录 2.3.2列表2.3.2.1列表介绍2.3.2.1.1列表的格式2.3.2.1.2打印列表 2.3.2.2列表的增删改查2.3.2.2.1列表的遍历2.3.2.2.1.1使用for循环2.3.2.2.1.2使用while循环 2.3.2.2.2添加元素("增"append, extend, insert)2.3.2.2.2.1append 2.3.2.2.2.2extend2.3.2.2.2…

博客搭建(hexo+github)

简介 搭建完成网站的如下所示 https://polarday.top/ 使用github托管博客&#xff0c;完全免费不需要购买服务器 博客框架&#xff1a;hexo hexo主题&#xff1a;ICARUS 图床&#xff1a;githubPicGo 编辑&#xff1a;vscode 为什么使用hexo框架&#xff1f;因为hexo是静态框…

新手开抖店:选品过后如何有效对接达人?这些方法100%有效!

哈喽~我是电商月月 要说做抖音小店最主要的是什么&#xff1f;那当然是找品了 那出单最快的方法是什么&#xff1f;无疑是达人带货了&#xff01; 但新手店铺没销量&#xff0c;没体验分&#xff0c;没好评怎么能让达人同意帮我们带货呢&#xff1f; 方法其实很简单&#x…

上位机图像处理和嵌入式模块部署(qmacvisual之plc通信)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 传统的非标自动化设备当中&#xff0c;plc发挥了很大的作用。这里面如何对这些电机和机构进行控制&#xff0c;大多数场景下用的就是plc设备了。目…

常用的AI绘画自动生成器介绍

AI绘画自动生成器是一种利用人工智能技术生成图像的工具。它可以根据用户输入的文本描述自动生成相应的图像。目前,有几种流行的AI绘画自动生成器,包括: 1. **DALL-E 2** DALL-E 2是由OpenAI开发的AI绘画生成器,它可以根据用户输入的自然语言描述生成高质量的图像。DALL-E 2使…

上位机图像处理和嵌入式模块部署(qmacvisual之tcp服务器端)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 上面一篇&#xff0c;我们谈到了tcp客户端&#xff0c;另外一种连接方法就是tcp服务器端。事实上&#xff0c;对于第三方系统&#xff0c;大多数情…