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

news2024/10/7 13:20:50

问题场景

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

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

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

相关文章

Windows集群部署项目

目录 一&#xff0c;环境准备 1.1.安装MySQL 1.2.安装JDK 1.3.安装TomCat 1.4.安装Nginx 二&#xff0c;部署 2.1.后台服务部署 2.2.Nginx配置负载均衡及静态资源部署 一&#xff0c;环境准备 1.1.安装MySQL 可以参考博客&#xff1a;http://t.csdnimg.cn/A75bg 1.2.…

我为什么会选择Vim来开发Go项目及Vim IDE安装配置和操作

你好&#xff0c;我是孔令飞&#xff0c;字节跳动云原生资深研发、前腾讯云原生技术专家。《企业级 Go 项目开发实战》、《从零开发企业级 Go 应用》作者&#xff0c;欢迎加入 孔令飞的云原生实战营&#xff0c;助你进阶 Go 云原生高级开发工程师。 作为一名 Golang 开发&…

训练营十六天(二叉树part03)

104.二叉树的最大深度 力扣题目链接(opens new window) 题目 给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 示例&#xff1a; 给定二叉树 [3,9,20,null,null,15,7]&…

从头开发一个RISC-V的操作系统(四)嵌入式开发介绍

文章目录 前提嵌入式开发交叉编译GDB调试&#xff0c;QEMU&#xff0c;MAKEFILE练习 目标&#xff1a;通过这一个系列课程的学习&#xff0c;开发出一个简易的在RISC-V指令集架构上运行的操作系统。 前提 这个系列的大部分文章和知识来自于&#xff1a;[完结] 循序渐进&#x…

leetcode 热题 100(部分)C/C++

leetcode 热题 100 双指针 盛最多水的容器 【mid】【双指针】 思路&#xff1a; 好久没写代码sb了&#xff0c;加上之前写的双指针并不多&#xff0c;以及有点思维定势了。我对双指针比较刻板的印象一直是两层for循环i&#xff0c;j&#xff0c;初始时i,j都位于左界附近&…

MySQL数据库 数据库基本操作(三):表的增删查改(中)

1. 数据库的约束 1.1 约束类型(一般发生于表的创建中) NOT NULL - 指示某列不能存储 NULL 值。UNIQUE - 保证某列的每行必须有唯一的值。DEFAULT - 规定没有给列赋值时的默认值。PRIMARY KEY - NOT NULL 和 UNIQUE 的结合。确保某列&#xff08;或两个列多个列的结合&#xf…

【CSS】浮动笔记及案例

CSS浮动 1. 认识浮动 float属性可以指定一个元素沿着左侧或者是右侧放置&#xff0c;允许文本和内联元素环绕它 float属性最初只使用文字环绕图片但却是早起CSS最好用的左右布局方案 绝对定位、浮动都会让元素脱标&#xff0c;以达到灵活布局的目的可以通过float属性让元素脱…

FTP,NFS,SSH服务安装和应用,遇到的问题

文章目录 一、FTP是什么&#xff1f;二、NFS是什么&#xff1f;三、SSH是什么&#xff1f;四、FTP&#xff0c;NFS&#xff0c;SSH安装(Ubuntu 操作系统)1.FTP安装2.NFS安装3.SSH服务安装 五、FTP&#xff0c;NFS&#xff0c;SSH应用1.FTP应用12.FTP应用23.NFS应用4.SSH应用 六…

【快速解决】python缺少了PyQt5模块的QtMultimedia子模块

目录 问题描述 问题原因 解决方法 成功示范 问题描述 Traceback (most recent call last): File "d:\桌面\python项目\DesktopWords-master\main.py", line 4, in <module> from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent ModuleNotFoundEr…

动态规划刷题(算法竞赛、蓝桥杯)--线段(线性DP)

1、题目链接&#xff1a;P3842 [TJOI2007] 线段 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include <bits/stdc.h> using namespace std; const int N20010; int a[N][2],f[N][2]; //a[i][0]表示l[i],a[i][1]表示r[i] int dis(int a,int b){return abs(a-b); } int…

计算机网络-HTTP相关知识-基础

HTTP基础 基本概念&#xff1a;HTTP是一种计算机之间交流通信的规范&#xff0c;它允许数据在两点之间传输&#xff0c;这个过程可以包括中转或接力。HTTP不仅仅包括文本&#xff0c;还可以包括图片、音频等超文本。状态码&#xff1a;HTTP状态码分为五类&#xff1a; 2xx&…

12-项目部署_持续集成

项目部署_持续集成 1 今日内容介绍 1.1 什么是持续集成 持续集成&#xff08; Continuous integration &#xff0c; 简称 CI &#xff09;指的是&#xff0c;频繁地&#xff08;一天多次&#xff09;将代码集成到主干 持续集成的组成要素 一个自动构建过程&#xff0c; 从…

BLE协议—HID

BLE协议—HID HID设备HOGP&#xff08;HID Over GATT Profile&#xff09;HID服务HID infoHID ModeHID ReportHID MAP HID设备 HID&#xff08;Human Interface Device&#xff0c;人机接口设备&#xff09;是USB设备中常用的设备类型&#xff0c;是直接与人交互的USB设备&…

PostgreSQL 文章下架 与 热更新和填充可以提升数据库性能

开头还是介绍一下群&#xff0c;如果感兴趣PolarDB ,MongoDB ,MySQL ,PostgreSQL ,Redis, Oceanbase, Sql Server等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;&#xff08;…

keycloak - 鉴权VUE

目录 一、前言 1、背景 2、实验版本 二、开始干活 1、keycloak配置 a、创建领域(realms) b、创建客户端 c、创建用户、角色 2、vue代码 a、依赖 b、main.js 三、未解决的问题 目录 一、前言 1、背景 2、实验版本 二、开始干活 1、keycloak配置 a、创建领域(r…

常见的数据库操作

一、查看数据库及表 1.查看当前 DMBS 中有哪些数据库 show databases; (首先进入数据库) 2.查看当前数据库中有哪些表 show tables; 3.查看表的结构&#xff08;信息&#xff09; describe 表名;如&#xff1a;describe orders; 二、创建删除数据库 1.创建数据库 create databa…

三栏布局——面试/笔试题

目录 三栏布局(两端指定宽度&#xff0c;中间自适应)三栏布局(平均分布) 三栏布局(两端指定宽度&#xff0c;中间自适应) 只介绍简单的写法&#xff0c;圣杯布局之类的比较复杂&#xff0c;实际上越简单越好&#xff0c;所以复杂的就不介绍了 flex布局 <!DOCTYPE html>…

博客搭建(hexo+github)

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

阿里云99元服务器性能测评_CPU内存_带宽_系统盘

阿里云服务器99元一年配置为云服务器ECS经济型e实例&#xff0c;2核2G配置、3M固定带宽和40G ESSD Entry系统盘&#xff0c;新用户和老用户均可买&#xff0c;续费不涨价依旧是99元一年&#xff0c;阿里云服务器网aliyunfuwuqi.com来详细说下阿里云99元服务器性能测评&#xff…

opencv使用问题记录一二

opencv介绍 opencv是一个计算机视觉处理软件库&#xff0c;拥有强大的功能和高效的性能。 但是由于早期版本的原因&#xff0c;存在一些与目前主流使用不兼容的问题 问题与解决 RGB通道顺序 一般图片处理类库的通道顺序就是RGB&#xff0c;但是opencv的是反过来的&#xf…