首页 > 其他分享 >mybatis查询大批量数据的几种方式

mybatis查询大批量数据的几种方式

时间:2023-09-14 15:59:19浏览次数:56  
标签:分页 StopWatch 大批量 查询 stopWatch 全量 mybatis 内存

问题背景

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

常规解决方案

  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

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

标签:分页,StopWatch,大批量,查询,stopWatch,全量,mybatis,内存
From: https://www.cnblogs.com/sun2020/p/17702696.html

相关文章

  • MyBatis框架详解:一个高效、灵活且易于使用的ORM框架
    MyBatis(原名ibatis)是一个基于Java语言的ORM(对象关系映射)框架,可以将数据库表中的数据转换成Java对象,也可以将Java对象中的数据插入到数据库表中。它采用了数据映射文件和注解两种方式来实现数据的映射,可以方便地管理SQL语句和实体类之间的关系。灵活性相比于其他ORM框架,MyBatis更加......
  • MySQL性能优化之 - 单表查询+代码层拼接 VS 表连接查询
    单表查询+代码端拼接的优势记得当初单位派我去阿里交流学习时,人家就说,在阿里,95%以上的查询都是单表查询,虽然我们都知道单表查询更加符合MySql底层的算法逻辑,但是单表查询+代码端拼接的优势究竟是什么,它为什么互联网企业都会使用单表查询呢?归纳而言大体分以下几点:1.激活代码端和......
  • SQL基础总结(七):子查询
    本系列blog源自前年写的SQL学习笔记,汇总一下发上来。(1月份发了前三篇笔记,原以为后面的笔记误操作删了,今天在硬盘里又找到了,一起发上来)--------------------------------不要在子查询中使用ORDERBY子句,子查询返回的中间结果是看不到的,对子查询排序没有意义。子查询是单个SELECT......
  • Citrix SQL数据库查询和替换修改字段
    1如下图,右键指定库新建查询即可2粘贴底部代码后,如下,点击执行查询即可--查看Citrix站点数据库如下表内容字段select*fromchb_config.Controllers;select*fromADIdentitySchema.Services;select*fromHostingUnitServiceSchema.Services;select*fromAppLibrarySchem......
  • Mysql慢查询优化
    Mysql慢查询优化实战效果:效率提升十倍左右优化前mysql>usetest_old;Databasechangedmysql>setprofiling='ON';QueryOK,0rowsaffectedmysql>showvariableslike'profiling';+---------------+-------+|Variable_name|Value|+--------......
  • 2、关于索引的二次查询
    聚集索引VS非聚集索引(B+树)超级详细讲解【字节跳动大佬】(MySQL索引-B+树(看完你就明白了)-苍青浪-博客园(cnblogs.com))在上节介绍B+树索引的时候,我们提到了图中的索引其实是聚集索引的实现方式。那什么是聚集索引呢?在MySQL中,B+树索引按照存储方式的不同分为聚集索引和......
  • 嵌套查询
    nested子文档在ES内部其实也是独立的lucene文档,只是我们在查询的时候,ES内部帮我们做了类似数据库的join处理。最终看起来好像是一个独立的文档一样。如果一个订单,有1000个订单项,那么在ES中存在的文档数就是1001,会随着订单数的增加而成倍上升。那可想而知,同样的条件下,这个......
  • 升讯威在线客服系统的并发高性能数据处理技术:PLINQ并行查询技术
    我在业余时间开发维护了一款免费开源的升讯威在线客服系统,也收获了许多用户。对我来说,只要能获得用户的认可,就是我最大的动力。最近客服系统成功经受住了客户现场组织的压力测试,获得了客户的认可。客户组织多名客服上线后,所有员工同一时间打开访客页面疯狂不停的给在线客服发消......
  • SQL Server 远程跨数据库服务器查询、导入数据(SQL)
    /*1、创建链接服务器查询*/--创建链接服务器execsp_addlinkedserver'remote_server','','SQLOLEDB','远程服务器名或ip地址'execsp_addlinkedsrvlogin'remote_server','false',null,'用户名','密码'--查询示例......
  • 商品查询业务之解决商品查询的缓存穿透、缓存雪崩、缓存击穿问题(封装工具类)
    商品查询业务之解决商品查询的缓存穿透、缓存雪崩、缓存击穿问题(封装工具类)核心思路如下:在原来的逻辑中,我们如果发现这个数据在mysql中不存在,直接就返回404了,这样是会存在缓存穿透问题的现在的逻辑中:如果这个数据不存在,我们不会返回404,还是会把这个数据写入到Redis中,并且将v......