Mybatis流式游标查询-大数据DB查询OOM查询问题

2023-11-11

问题场景

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)普通查询,大概接近200多M就GC释放

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

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

执行原理—分析

参考:https://machen.blog.csdn.net/article/details/112169908

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 端建立起连接并且交互查询时,MySQLServer 会通过输出流将SQL 结果集返回输出,也就是 向本地的内核对应的 SocketBuffer 中写入数据,然后将内核中的数据通过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定义-----fetchSize为Integer.MIN_VALUE

<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结束才结束事务,不然会出现只能读取到第一段游标的结果集。

@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数据,游式查询能支持,但也不能一起性放入java的list中,内存不够还是会溢出。这时可能就需要写一些条件一次处理多少数据,所以本质来说就是数据不一次性存储,但总有地方要把这些数据存着。不给JVM内存,那就会牺牲网络或者服务器的其它属性。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Mybatis流式游标查询-大数据DB查询OOM查询问题 的相关文章

  • HTAccess - 令人困惑的干净网址

    如果我想简单地重定向 clients page to clients php view page我会使用像这样简单的东西 它效果很好 Options FollowSymlinks RewriteEngine on RewriteRule c
  • MySQL 导入 125000 行 CSV 的最快方法?

    这是我第一次使用 MySQL 除了对现有数据库进行一些基本查询之外 所以我不擅长解决这个问题 我有一个包含 125 000 条记录的 CSV 我想将其加载到 MySQL 中 我安装了版本 8 和工作台 我使用导入向导加载 CSV 它开始导入
  • Spring 3.1 + Hibernate 4.1 Propagation.Supports 问题

    我正在将我的项目从 Spring 3 0 hibernate 3 6 x 迁移到 S3 1 H4 1 我的新代码如下
  • Java 从 SQL 数组获取 ResultSet 失败

    我试图从数据库中检索电子邮件地址 但没有成功 我的代码如下 Main System out println PortfolioData getEmails 58 So Far Returning null 投资组合数据 public sta
  • 在 MySQL 数据库中存储图像文件或 URL?哪个更好? [复制]

    这个问题在这里已经有答案了 可能的重复 在数据库中存储图像 是还是否 https stackoverflow com questions 3748 storing images in db yea or nay 数据库中的图像与文件系统中的
  • SQL统计高于和低于平均分的学生人数

    我在下面有一个示例表 我试图获取高于平均分数的学生人数和低于平均分数的学生人数 name subject classroom classarm session first term score first term grade std1 m
  • SQL 检查一组日期是否在指定的日期范围内

    我有一个表 其中保存架构中房间不可用的日期 ROOM ID DATE UNAVAILABLE 我需要一个 sql 查询来检查两个日期范围内是否有可用房间 类似于 Select All rooms that are constantly av
  • MySQL用户创建的临时表已满

    我使用内存引擎创建了一个临时表 如下所示 CREATE TEMPORARY TABLE IF NOT EXISTS some text id INT DEFAULT 0 string varchar 400 DEFAULT engine m
  • 如何使PHP中的激活链接过期?

    我有一个 php 脚本 它通过电子邮件向用户发送激活链接 以便他们可以激活他们的帐户 链接是这样的 mysite com activation phpid id 20 如何创建 24 小时后过期的链接 我还没有尝试过任何东西 因为我找不到任
  • Codeigniter 加入多个条件

    我正在使用 Codeigniter Active Records 课程 我想加入我的users与我的桌子clients表 这样我就可以显示用户的 真实 姓名 而不仅仅是他们的 ID 这是什么clients表看起来像 示例 列 a 1 a 2
  • 使用唯一索引删除重复项

    我在两个表字段 A B C D 之间插入 相信我已经在 A B C D 上创建了唯一索引以防止重复 然而我以某种方式简单地对这些做了一个正常的索引 因此插入了重复项 这是2000万条记录的表 如果我将现有索引从普通索引更改为唯一索引 或者只
  • mysql语句中的*星号是什么意思?

    Ex mysql query SELECT FROM members WHERE id id 这意味着选择表中的所有列
  • 通过货币换算获取每种产品类型的最低价格

    我想选择每种产品类型中最便宜的 包括运费 价格转换为当地货币 最便宜 产品 价格 产品 运费 seller to aud 我的数据库有如下表 PRODUCTS SELLERS id type id seller id price shipp
  • C#:SQL 查询生成器类

    在哪里可以找到好的 SQL 查询构建器类 我只需要一个简单的类来构建 SQL 字符串 仅此而已 我需要它用于 C 和 MySql 我真的不需要像 Linq 或 NHibernate 这样的东西 谢谢 由于 Google 将我引导至此页面 我
  • MySQL 服务器未启动

    当我做 mysql u root p并输入my password这就是我得到的 错误 2002 HY000 无法通过套接字 var run mysqld mysqld sock 连接到本地 MySQL 服务器 2 所以我输入 systemc
  • PHP PDO相关:更新SQL语句未更新数据库内容

    我正在尝试使用准备好的语句来实现更新语句PHP http en wikipedia org wiki PHP脚本 但它似乎没有更新数据库中的记录 我不确定为什么 所以如果您能分享一些见解 我将不胜感激 Code query UPDATE D
  • 使用按位函数查询 BIT 字段时,MySQL 不使用索引

    我的 MySQL 表中有一个 BIT 类型的字段 我想使用位值存储记录的状态 例如 1 status1 2 status2 4 status3 8 status4 每条记录可以同时具有多种状态 对于 status1 和 status3 该值
  • PHP 选择后立即删除

    我有一个 PHP 服务器脚本 它从 MySQL 数据库中选择一些数据 一旦我将 mysql query 和 mysql fetch assoc 的结果存储在我自己的局部变量中 我就想删除我刚刚选择的行 这种方法的问题在于 PHP 似乎对我的
  • 显示表 FULLTEXT 索引列

    我希望运行一个查询 该查询将返回表中全文索引的列列表 该表采用 MyISAM 格式 我将使用 php 来构建查询 理想情况下 我会运行查询 它会返回信息 以便我可以构造一个以逗号分隔的列字符串 例如 名 姓 电子邮箱 这在 MySQL 中可
  • 无法启动 MySQL 服务器 - 控制进程退出并出现错误代码

    我的 mysql 服务器停止后无法启动 命令使用 sudo etc init d mysql restart Error 重新启动 mysql 通过 systemctl mysql serviceJob for mysql service

随机推荐

  • nohup 命令之 nohup: ignoring input and appending output to ‘nohup.out’

    当我在linux中使用nohup 命令的时候 出现了这个错误 nohup ignoring input and appending output to nohup out 忽略输入输出 将信息化信息记录到nohup out文件中 补充 no
  • Vue3 学习

    基础 js https www bilibili com video BV15T411j7pJ spm id from 333 337 search card all click vd source 9747207be61edfe4ec62
  • 中标麒麟V7系统开启日志服务器收集日志

    0 备份 编辑配置文件 cp etc rsyslog conf etc rsyslog conf bak20210427 vi etc rsyslog conf 1 修改服务端配置 打开服务监听端口 MODULES The imjourna
  • UV纹理与贴图

    文章转载自 http www cnblogs com jenry p 4083415 html 1 什么是UV 对于三维模型 有两个最重要的坐标系统 一是顶点的位置 X Y Z 坐标 另一个就是UV坐标 什么是UV 简单的说 就是贴图影射到
  • 旋转后计算法线

    11
  • base64 编码转png上传minio

    public static InputStream base64ConvertPNG String base64 BASE64Decoder decoder new BASE64Decoder 解码 base64 base64 replac
  • opencv 中findContours函数介绍

    opencv4 中findContours函数介绍 findContours 这个函数的主要功能是找到contours也就是边界线 不过这个函数主要是给灰度图对象服务的 函数的使用介绍 findContours image mode met
  • MD5 Hash + Salt的密码存储方式实现

    MD5算法是一种密码散列算法 MD5散列后的数据长度为128 bit 该散列算法在密码上使用得非常多 但现在已经被更安全的SHA 1代替 MD5算法产生的密码可以使用反向查询的方式从预先算出的密码MD5库中查到密码 网上的一些MD5库中大约
  • 动态加载js的三种方式,以及script的三种加载方式

    测试准备 1 html文件 2 测试的js文件 我这里准备了四个js文件 每个脚本的最后都加上了console log 文件名 src main js 135KB src tooltip js gt 17KB src button js 4
  • 数据库五表关联查询

    1基于角色访问控制RBAC 1 1RABC数据库设计 1 2RABC实现流程 基于菜单实现 在创建系统用户时 为用户绑定对应的角色 当登录成功进入主页面 根据用户查询角色 再根据角色查询到当前用户的权限列表 2主页菜单的加载显示功能 根据不
  • Qt 使用 windeployqt 打包软件

    Qt 使用 windeployqt 打包软件 需使用qt 自带的终端打包 例如 打开之后 运行命令 windeployqt exe 需要打包的exe 不要使用cmd或者powershell
  • selenium知识点小结

    环境 Python 3 6 1 selenium 3 4 3 Firefox 54 0 1 geckodriver 0 15 0 chrome 59 0 3 chromedriver v3 1 自动化测试基础 1 软件测试分类 根据项目流程
  • chineseocr 安装笔记 2021-05

    chineseocr 基于yolo3 与crnn 实现中文自然场景文字检测及识别 选在 GitHub 目前位置有 4 1K的star 也正因为基于 yolo 的原因 尝试一下 chineseocr 的中文识别 GitHub地址 GitHub
  • 替换spring的commons-logging(jcl)日志系统为logback日志系统

    接口层名称 说明 JCL Jakarta Commons Logging 陈旧 这个就是spring core依赖的logging SLF4J 适合 同一作者 jboss logging 特殊专业领域使用 实现层名称 说明 log4j 最初
  • CKA 核心知识点概述

    CKA 核心知识点概述 介绍 知识点 说明 介绍 笔者曾经在本专栏分享了大量 CKA CKS 考点相关的内容 由于某些原因被 jubao 下架 但考虑到很多网友和群友想进一步了解 CKA 相关的核心知识 因此在这里把笔者之前的记录整理了一下
  • RuoYi-Vue————权限管理

    RuoYi Vue 权限管理 1 若依框架权限分类 2 若依框架权限的依次介绍 3 若依框架重要接口执行流程 1 若依框架权限分类 若依Vue系统中的权限分为以下几类 1 菜单权限 用户登录系统之后能看到哪些菜单 2 按钮权限 用户在一个页
  • 微信小程序基础知识-----获取用户授权信息

    点允许 绑定用户信息 用户头像和昵称 效果图 如何获取昵称和头像后将绿框框消除 设定一个数据变量 将设定的变量作为条件渲染 当获取到的用户信息后将设定的变量值改为false 执行这一板块 效果图 如何解决头像的问题
  • Vue2和Vue3自定义组件使用v-model

    Vue2 父组件 v model 自定义组件 props接收 model对象里写prop和event method里写具体方法 子组件
  • win11配置wsl2以及深度学习环境

    安装wsl2 许多深度学习代码都是使用Linux跑的 跑别人的代码 最好环境可以一样 如果使用Windows可能会遇到很多bug wsl2相比于wsl有着些许升级 对于深度学习来说 好处是可以使用CUDA 这里演示win11安装wsl2 打
  • Mybatis流式游标查询-大数据DB查询OOM查询问题

    问题场景 Mysql数据处理类型分以下三种 com mysql cj protocol a result ResultsetRowsStatic 普通查询 将结果集一次性全部拉取到内存 com mysql cj protocol a res