mybatis查询大批量数据的几种方式
问题背景
公司里有很多需要跑批数据的场景,这些数据几十万到几千万不等,目前我们采用的是分页查询,但是分页查询有个深度分页问题,上百万的数据就会查询的很慢
常规解决方案
- 全量查询
- 分页查询
- 流式查询
- 游标查询
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查询大批量数据的几种方式的更多相关文章
- android sqlite使用之模糊查询数据库数据的三种方式
android应用开发中常常需要记录一下数据,而在查询的时候如何实现模糊查询呢?很少有文章来做这样的介绍,所以这里简单的介绍下三种sqlite的模糊查询方式,直接上代码把: package com.e ...
- mybatis批量添加数据的三种方式
原文地址:https://www.cnblogs.com/gxyandwmm/p/9565002.html
- mybatis中批量插入的两种方式(高效插入)
MyBatis简介 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以使用 ...
- 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 ...
- Solr 删除数据的几种方式
原文出处:http://blog.chenlb.com/2010/03/solr-delete-data.html 有时候需要删除 Solr 中的数据(特别是不重做索引的系统中,在重做索引期间).删除 ...
- 查询json数据结构的8种方式
查询json数据结构的8种方式 你有没有对“在复杂的JSON数据结构中查找匹配内容”而烦恼.这里有8种不同的方式可以做到: JsonSQL JsonSQL实现了使用SQL select语句在json数 ...
- MyBatis开发Dao层的两种方式(原始Dao层开发)
本文将介绍使用框架mybatis开发原始Dao层来对一个对数据库进行增删改查的案例. Mapper动态代理开发Dao层请阅读我的下一篇博客:MyBatis开发Dao层的两种方式(Mapper动态代理方 ...
- MyBatis开发Dao层的两种方式(Mapper动态代理方式)
MyBatis开发原始Dao层请阅读我的上一篇博客:MyBatis开发Dao层的两种方式(原始Dao层开发) 接上一篇博客继续介绍MyBatis开发Dao层的第二种方式:Mapper动态代理方式 Ma ...
- Day20-单表中获取表单数据的3种方式
1. 搭建环境请参考:http://www.cnblogs.com/momo8238/p/7508677.html 2. 创建表结构 models.py from django.db import m ...
- python爬虫---爬虫的数据解析的流程和解析数据的几种方式
python爬虫---爬虫的数据解析的流程和解析数据的几种方式 一丶爬虫数据解析 概念:将一整张页面中的局部数据进行提取/解析 作用:用来实现聚焦爬虫的吧 实现方式: 正则 (针对字符串) bs4 x ...
随机推荐
- MySQL常用操作指令大全
前言: 一.基础概念 二.子句顺序 三.使用MySQL(USE.SHOW) 四.检索数据(SELECT) 五.排序检索数据(ORDER BY) 六.过滤数据(WHERE) 七.数据过滤(AND.OR. ...
- nodejs连接mysql报错:throw err; // Rethrow non-MySQL errors TypeError: Cannot read property 'query' of undefined
该问题的解决方案如下: win+R 输入cmd mysql -u root -p 输入密码进入到mysql 3.执行sql语句,将密码改成123456(自己可以记住的密码即可) alter user ...
- 遥感图像处理笔记之【Land use/Land cover classification with Deep Learning】
遥感图像处理学习(1) 前言 遥感图像处理方向的学习者可以参考或者复刻 本文初编辑于2023年12月14日CSDN平台 2024年1月24日搬运至本人博客园平台 文章标题:Land use/Land ...
- CF452F Permutation 与 P2757 [国家集训队] 等差子序列 题解
两道基本一样的题: 题目链接: P2757 [国家集训队] 等差子序列 Permutation 链接:CF 或者 洛谷 等差子序列那题其实就是长度不小于 \(3\) 的等差数列是否存在,我们考虑等于 ...
- Redis的Java客户端-Jedis
Redis的Java客户端-Jedis 在Redis官网中提供了各种语言的客户端,地址:https://redis.io/docs/clients/ 其中Java客户端也包含很多: 标记为的就是推荐使 ...
- 小知识:如何修改TFA下的OSW数据保留时间
在Oracle社区可以搜索到这样的问题: How to change oswatcher retention when running under TFA 但很遗憾该问题目前也没有给出确切答复. 其实 ...
- 存储过程分页以及参数拼接sql语句、C#调用存储过程
1.C#调用存储过程,带参数返回的功能,而且是参数化拼接,这样就可以防止sql注入 System.Data.SqlClient.SqlParameter[] parameters = { new Sy ...
- 收集 VSCode 常用快捷键
快速复制行 Shift + Alt + ↑/↓ 都是往下复制行,区别是:按↓复制时光标会跟着向下移动,按↑复制时光标不移动. 向上/向下移动一行 Alt + ↑/↓ 删除整行 Ctrl + Shift ...
- 基于 log4j2 插件实现统一日志脱敏,性能远超正则替换
前言 金融用户敏感数据如何优雅地实现脱敏? 日志脱敏之后,无法根据信息快速定位怎么办? 经过了这两篇文章之后,我们对日志脱敏应该有了一定的理解. 但是实际项目中,我们遇到的情况往往更加复杂: 1)项目 ...
- 项目实战:Qt监测操作系统物理网卡通断v1.1.0(支持windows、linux、国产麒麟系统)
需求 使用Qt软件开发一个检测网卡的功能. 兼容windows.linux,国产麒麟系统(同为linux) Demo windows上运行: 国产麒麟操作上运行: 功 ...