问题背景

公司里有很多需要跑批数据的场景,这些数据几十万到几千万不等,目前我们采用的是分页查询,但是分页查询有个深度分页问题,上百万的数据就会查询的很慢

常规解决方案

  1. 全量查询
  2. 分页查询
  3. 流式查询
  4. 游标查询

1. 全量查询

默认情况下,全量查询的话系统会把所有结果集存储在内存中,在数据库中准备了大概200w的数据:

<select id="listUser" resultType="com.sun.ddd.infra.po.User">
select * from user
</select>
@Test
public void test() {
StopWatch stopWatch = new StopWatch();
stopWatch.start("全量查询");
List<User> users = userService.listUser();
stopWatch.stop();
System.out.println(stopWatch.getLastTaskName() + ":" + stopWatch.getLastTaskTimeMillis() + ":代码行数:" + users.size());
}
全量查询:21757:代码行数:2778523

利用JDK自带的java VisualVM监控全量查询时的内存占用情况

  • 可以很明显的看出200w的数据一次性查询占用总体内存1500MB,这个内存占用还是很大的,如果还有其他服务在运行,很容易导致OOM

2. 分页查询

为了解决全量查询占用内存过大,可能导致OOM问题,我们可以选择使用分页查询,这样就不会导致内存溢出问题了

@Override
public List<User> pageUser(Integer pageNum, Integer pageSize) {
pageNum = (pageNum - 1) * pageSize;
return userDao.pageUser(pageNum, pageSize);
}
<select id="pageUser" resultType="com.sun.ddd.infra.po.User">
select * from user limit #{pageNum},#{pageSize}
</select>
@Test
public void test() {
StopWatch stopWatch = new StopWatch();
stopWatch.start("分页查询");
int pageCount = 0;
for (int i = 1; i < 1000; i++) {
List<User> users1 = userService.pageUser(i, 2000);
pageCount = pageCount + users1.size();
}
stopWatch.stop();
System.out.println(stopWatch.getLastTaskName() + ":" + stopWatch.getLastTaskTimeMillis() + ":代码行数:" + pageCount);
}
分页查询:285343:代码行数:1998000

  • 使用分页后,查询内存使用情况,最多占用内存不到500MB,是全量查询占用内存的1/3不到,但是由于深度分页和多次与数据库连接的缘故,导致整个查询时间很长,长达280s,如果数据更多点查询时间则更多

3. 流式查询

那有没有什么方式,可以查的又快,占用内存又小呢?答案当然是有的了

客户端 JDBC 发起 SQL 查询,等待服务端准备数据。MySQL 服务端会向 JDBC 代表的客户端内核源源不断的输送数据,直到客户端请求 Socket 缓冲区满,这时的 MySQL服务端会阻塞。对于 JDBC 客户端而言,数据每次读取都是从本机器的内核缓冲区,所以性能会更快一些。类似服务端向客户端不断push的过程

是否使用流式的标志:

 /**
* We only stream result sets when they are forward-only, read-only, and the
* fetch size has been set to Integer.MIN_VALUE
*
* @return true if this result set should be streamed row at-a-time, rather
* than read all at once.
*/
protected boolean createStreamingResultSet() {
return ((this.query.getResultType() == Type.FORWARD_ONLY) && (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY)
&& (this.query.getResultFetchSize() == Integer.MIN_VALUE));
}

其中我们只要关注this.query.getResultFetchSize() == Integer.MIN_VALUE,对应xml配置就是fetchSize="-2147483648"

<select id="listUserByStream" fetchSize="-2147483648" resultType="com.sun.ddd.infra.po.User">
select * from user
</select>

这里mapper接口不需要返回值,因为数据都存储在ResultHandler<User>中了

void listUserByStream(ResultHandler<User> handler);
@Test
public void test() {
StopWatch stopWatch = new StopWatch();
stopWatch.start("流式查询");
AtomicInteger totalCount = new AtomicInteger(0);
userService.listUserByStream(context -> {
// 处理查询结果
context.getResultObject();
totalCount.incrementAndGet();
});
stopWatch.stop();
System.out.println(stopWatch.getLastTaskName() + ":" + stopWatch.getLastTaskTimeMillis() + ":代码行数:" + totalCount.get());
}
流式查询:9967:代码行数:2778523

  • 同样是200w数据,可以明显看出查询时间只要9s多,占用内存也保持在500MB之内

    4. 游标查询

    客户端 JDBC 发起 SQL 查询,等待服务端准备数据。服务端数据准备完成后,进行数据传输,它允许应用程序在数据库服务器上打开一个游标并按需检索数据,而不是一次性获取整个结果集,类似客户端向服务端分批pull的过程。

    mapper接口层接收参数方式使用Cursor<User>

    Cursor<User> listUserByCursor();
    <select id="listUserByCursor"  fetchSize="-2147483648" resultType="com.sun.ddd.infra.po.User">
    select * from user
    </select>
    @Test
    @Transactional
    public void test() {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start("游标查询");
    AtomicInteger totalCountCursor = new AtomicInteger(0);
    Cursor<User> users2 = userService.listUserByCursor();
    for (User user : users2) {
    totalCountCursor.incrementAndGet();
    }
    stopWatch.stop();
    System.out.println(stopWatch.getLastTaskName() + ":" + stopWatch.getLastTaskTimeMillis() + ":代码行数:" + totalCountCursor.get());
    }

    由于Cursor是一条条查,所以会关闭会话,需要在方法上加@Transactional即可

    游标查询:9813:代码行数:2778523

  • 从测试结果来看,查询200w条数据时间跟流式查询差不多,占用的内存也不到500MB

总结:

查询方式 数据条数 查询时间 占用内存
全量查询 2778523 21757 1600MB
分页查询 1998000 285343 500MB
流式查询 2778523 9967 450MB
游标查询 2778523 9813 550MB

推荐使用流式查询,游标查询还跟指定数据库有关

mybatis查询大批量数据的几种方式的更多相关文章

  1. android sqlite使用之模糊查询数据库数据的三种方式

    android应用开发中常常需要记录一下数据,而在查询的时候如何实现模糊查询呢?很少有文章来做这样的介绍,所以这里简单的介绍下三种sqlite的模糊查询方式,直接上代码把: package com.e ...

  2. mybatis批量添加数据的三种方式

    原文地址:https://www.cnblogs.com/gxyandwmm/p/9565002.html

  3. mybatis中批量插入的两种方式(高效插入)

    MyBatis简介 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以使用 ...

  4. Linux就这个范儿 第15章 七种武器 linux 同步IO: sync、fsync与fdatasync Linux中的内存大页面huge page/large page David Cutler Linux读写内存数据的三种方式

    Linux就这个范儿 第15章 七种武器  linux 同步IO: sync.fsync与fdatasync   Linux中的内存大页面huge page/large page  David Cut ...

  5. Solr 删除数据的几种方式

    原文出处:http://blog.chenlb.com/2010/03/solr-delete-data.html 有时候需要删除 Solr 中的数据(特别是不重做索引的系统中,在重做索引期间).删除 ...

  6. 查询json数据结构的8种方式

    查询json数据结构的8种方式 你有没有对“在复杂的JSON数据结构中查找匹配内容”而烦恼.这里有8种不同的方式可以做到: JsonSQL JsonSQL实现了使用SQL select语句在json数 ...

  7. MyBatis开发Dao层的两种方式(原始Dao层开发)

    本文将介绍使用框架mybatis开发原始Dao层来对一个对数据库进行增删改查的案例. Mapper动态代理开发Dao层请阅读我的下一篇博客:MyBatis开发Dao层的两种方式(Mapper动态代理方 ...

  8. MyBatis开发Dao层的两种方式(Mapper动态代理方式)

    MyBatis开发原始Dao层请阅读我的上一篇博客:MyBatis开发Dao层的两种方式(原始Dao层开发) 接上一篇博客继续介绍MyBatis开发Dao层的第二种方式:Mapper动态代理方式 Ma ...

  9. Day20-单表中获取表单数据的3种方式

    1. 搭建环境请参考:http://www.cnblogs.com/momo8238/p/7508677.html 2. 创建表结构 models.py from django.db import m ...

  10. python爬虫---爬虫的数据解析的流程和解析数据的几种方式

    python爬虫---爬虫的数据解析的流程和解析数据的几种方式 一丶爬虫数据解析 概念:将一整张页面中的局部数据进行提取/解析 作用:用来实现聚焦爬虫的吧 实现方式: 正则 (针对字符串) bs4 x ...

随机推荐

  1. TienChin 渠道管理-添加渠道

    在我们平时新建一个全新的 Java 类,这个类需要存放的包不存在,可以使用如下的方式进行创建: 含义就是说,将 ChannelVO 这个类放在 vo 这个包当中,如果存在则不创建,存在就将新建的类放入 ...

  2. 数据挖掘机器学习[五]---汽车交易价格预测详细版本{模型融合(Stacking、Blending、Bagging和Boosting)}

    题目出自阿里天池赛题链接:零基础入门数据挖掘 - 二手车交易价格预测-天池大赛-阿里云天池 相关文章: 特征工程详解及实战项目[参考] 数据挖掘---汽车车交易价格预测[一](测评指标:EDA) 数据 ...

  3. C/C++ 反汇编:流程控制与循环结构

    反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解.外挂技术.病毒分析.逆向工程.软件汉化等领域,学习和理解反汇编对软件调试.系统漏洞挖掘.内核原理及理解高级语言代码都有相当大的帮助, ...

  4. django orm 筛选

    import os os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test.setting") import ...

  5. STM32CubeMX教程29 USB_HOST - 使用FatFs文件系统读写U盘

    1.准备材料 正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK-Arm) ST-LINK/V2驱动 ...

  6. How to 充分利用你的服务器

    服务器高性能计算指南 本文处于preview阶段,内容并不完全严谨,如有错误敬请原谅,适当参考. 目录 服务器高性能计算指南 什么样的计算适合当前服务器? 从CPU架构入手 两个CPU带来的影响 那么 ...

  7. 【Linux】常用基本指令大汇总系列(篇一)【超详细的图解保姆教程】

    常用基本指令大汇总系列(篇一) 大家好,欢迎大家来到我的博客.从今天开始,博主就要开启一个全新的系列了!Linux操作系统常用基本指令汇总系列,当大家按顺序学完这个系列,在Linux上编写基本的C语言 ...

  8. (C语言)每日代码||2023.12.25||函数传参,传入数组时传递的是数组首元素地址

    向函数传入某数组时,可以在函数内修改该数组的元素. #include <stdio.h> void test(char* p, char arr[]) { *p = 'h';//能改变 * ...

  9. 用set做一轮无重复纯随机

    前端时间面腾讯的时候,一位老师问了一个相当有趣的问题:假设存在一个音乐播放器,里面有一个100首歌的歌单.现在需要做一个随机播放功能,要求不重复的随机播放完一百首歌.当时脑子短路了没想出来,几天突然意 ...

  10. linux下使用find查找并操作文件

    介绍 最近在centos7上部署了一套环境,需要根据文件名找到程序运行路径下的文件,并进行移动文件操作,为此查阅了一番,记录下这个操作的脚本.我想很多人都会有这个需求,查找简单,但是要对对查找到的文件 ...