我的编程空间,编程开发者的网络收藏夹
学习永远不晚

MyBatis中怎么查询千万数据量

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

MyBatis中怎么查询千万数据量

这期内容当中小编将会给大家带来有关MyBatis中怎么查询千万数据量,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

常规查询

默认情况下,完整的检索结果集会将其存储在内存中。在大多数情况下,这是最有效的操作方式,并且由于 MySQL 网络协议的设计,因此更易于实现

假设单表 500w 数据量,没有人会一次性加载到内存中,一般会采用分页的方式

@SneakyThrows @Override public void pageQuery() {     @Cleanup Connection conn = dataSource.getConnection();     @Cleanup Statement stmt = conn.createStatement();     long start = System.currentTimeMillis();     long offset = 0;     int size = 100;     while (true) {         String sql = String.format("SELECT COLUMN_A, COLUMN_B, COLUMN_C FROM YOU_TABLE LIMIT %s, %s", offset, size);         @Cleanup ResultSet rs = stmt.executeQuery(sql);         long count = loopResultSet(rs);         if (count == 0) break;         offset += size;     }      log.info("  ??? 分页查询耗时 :: {} ", System.currentTimeMillis() - start); }

上述方式比较简单,但是在不考虑 LIMIT 深分页优化情况下,线上数据库服务器就凉了,亦或者你能等个几天时间检索数据

流式查询

如果你正在使用具有大量数据行的 ResultSet,并且无法在 JVM 中为其分配所需的内存堆空间,则可以告诉驱动程序从结果流中返回一行

MyBatis中怎么查询千万数据量

流式查询有一点需要注意:必须先读取(或关闭)结果集中的所有行,然后才能对连接发出任何其他查询,否则将引发异常

使用流式查询,则要保持对产生结果集的语句所引用的表的并发访问,因为其 查询会独占连接,所以必须尽快处理

@SneakyThrows public void streamQuery() {     @Cleanup Connection conn = dataSource.getConnection();     @Cleanup Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);     stmt.setFetchSize(Integer.MIN_VALUE);     long start = System.currentTimeMillis();     @Cleanup ResultSet rs = stmt.executeQuery("SELECT COLUMN_A, COLUMN_B, COLUMN_C FROM YOU_TABLE");     loopResultSet(rs);     log.info("  ??? 流式查询耗时 :: {} ", (System.currentTimeMillis() - start) / 1000); }

流式查询库表数据量 500w 单次调用时间消耗:≈ 6s

游标查询

SpringBoot 2.x 版本默认连接池为 HikariPool,连接对象是  HikariProxyConnection,所以下述设置游标方式就不可行了

((JDBC4Connection) conn).setUseCursorFetch(true);

需要在数据库连接信息里拼接 &useCursorFetch=true。其次设置 Statement 每次读取数据数量,比如一次读取  1000

@SneakyThrows public void cursorQuery() {     @Cleanup Connection conn = dataSource.getConnection();     @Cleanup Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);     stmt.setFetchSize(1000);      long start = System.currentTimeMillis();     @Cleanup ResultSet rs = stmt.executeQuery("SELECT COLUMN_A, COLUMN_B, COLUMN_C FROM YOU_TABLE");     loopResultSet(rs);     log.info("  ??? 游标查询耗时 :: {} ", (System.currentTimeMillis() - start) / 1000); }

游标查询库表数据量 500w 单次调用时间消耗:≈ 18s

JDBC RowData

上面都使用到了方法 loopResultSet,方法内部只是进行了 while 循环,常规、流式、游标查询的核心点在于 next 方法

@SneakyThrows private Long loopResultSet(ResultSet rs) {     while (rs.next()) {     // 业务操作     }     return xx; }

ResultSet.next() 的逻辑是实现类 ResultSetImpl 每次都从 RowData 获取下一行的数据。RowData  是一个接口,实现关系图如下

MyBatis中怎么查询千万数据量

默认情况下 ResultSet 会使用 RowDataStatic 实例,在生成 RowDataStatic 对象时就会把 ResultSet  中所有记录读到内存里,之后通过 next() 再一条条从内存中读

RowDataCursor 的调用为批处理,然后进行内部缓存,流程如下:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 首先会查看自己内部缓冲区是否有数据没有返回,如果有则返回下一行

  3. 如果都读取完毕,向 MySQL Server 触发一个新的请求读取 fetchSize 数量结果

  4. 并将返回结果缓冲到内部缓冲区,然后返回第一行数据

当采用流式处理时,ResultSet 使用的是 RowDataDynamic 对象,而这个对象 next() 每次调用都会发起 IO 读取单行数据

总结来说就是,默认的 RowDataStatic 读取全部数据到客户端内存中,也就是我们的 JVM;RowDataCursor 一次读取  fetchSize 行,消费完成再发起请求调用;RowDataDynamic 每次 IO 调用读取一条数据

JDBC 通信原理

普通查询

在 JDBC 与 MySQL 服务端的交互是通过 Socket 完成的,对应到网络编程,可以把 MySQL 当作一个  SocketServer,因此一个完整的请求链路应该是:

JDBC 客户端 -> 客户端 Socket -> MySQL -> 检索数据返回 -> MySQL 内核 Socket 缓冲区  -> 网络 -> 客户端 Socket Buffer -> JDBC 客户端

普通查询的方式在查询大数据量时,所在 JVM 可能会凉凉,原因如下:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. MySQL Server 会将检索出的 SQL 结果集通过输出流写入到内核对应的 Socket Buffer

  3. 内核缓冲区通过 JDBC 发起的 TCP 链路进行回传数据,此时数据会先进入 JDBC 客户端所在内核缓冲区

  4. JDBC 发起 SQL 操作后,程序会被阻塞在输入流的 read 操作上,当缓冲区有数据时,程序会被唤醒进而将缓冲区数据读取到 JVM 内存中

  5. MySQL Server 会不断发送数据,JDBC 不断读取缓冲区数据到 Java 内存中,虽然此时数据已到 JDBC 所在程序本地,但是 JDBC  还没有对 execute 方法调用处进行响应,因为需要等到对应数据读取完毕才会返回

  6. 弊端就显而易见了,如果查询数据量过大,会不断经历 GC,然后就是内存溢出

游标查询

通过上文得知,游标可以解决普通查询大数据量的内存溢出问题,但是

小伙伴有没有思考过这么一个问题,MySQL 不知道客户端程序何时消费完成,此时另一连接对该表造成 DML 写入操作应该如何处理?

其实,在我们使用游标查询时,MySQL 需要建立一个临时空间来存放需要被读取的数据,所以不会和 DML 写入操作产生冲突

但是游标查询会引发以下现象:

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. IOPS 飙升,因为需要返回的数据需要写入到临时空间中,存在大量的 IO 读取和写入,此流程可能会引起其它业务的写入抖动

  3. 磁盘空间飙升,因为写入临时空间的数据是在原表之外的,如果表数据过大,极端情况下可能会导致数据库磁盘写满,这时网络输出时没有变化的。而写入临时空间的数据会在  读取完成或客户端发起 ResultSet#close 操作时由 MySQL 回收

  4. 客户端 JDBC 发起 SQL 查询,可能会有长时间等待 SQL 响应,这段时间为服务端准备数据阶段。但是  普通查询等待时间与游标查询等待时间原理上是不一致的,前者是一致在读取网络缓冲区的数据,没有响应到业务层面;后者是 MySQL 在准备临时数据空间,没有响应到  JDBC

  5. 数据准备完成后,进行到传输数据阶段,网络响应开始飙升,IOPS 由"读写"转变为"读取"

采用游标查询的方式 通信效率比较低,因为客户端消费完 fetchSize 行数据,就需要发起请求到服务端请求,在数据库前期准备阶段 IOPS  会非常高,占用大量的磁盘空间以及性能

流式查询

当客户端与 MySQL Server 端建立起连接并且交互查询时,MySQL Server 会通过输出流将 SQL 结果集返回输出,也就是  向本地的内核对应的 Socket Buffer 中写入数据,然后将内核中的数据通过 TCP 链路回传数据到 JDBC 对应的服务器内核缓冲区

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. JDBC 通过输入流 read 方法去读取内核缓冲区数据,因为开启了流式读取,每次业务程序接收到的数据只有一条

  3. MySQL 服务端会向 JDBC 代表的客户端内核源源不断的输送数据,直到客户端请求 Socket 缓冲区满,这时的 MySQL 服务端会阻塞

  4. 对于 JDBC 客户端而言,数据每次读取都是从本机器的内核缓冲区,所以性能会更快一些,一般情况不必担心本机内核无数据消费(除非 MySQL  服务端传递来的数据,在客户端不做任何业务逻辑,拿到数据直接放弃,会发生客户端消费比服务端超前的情况)

看起来,流式要比游标的方式更好一些,但是事情往往不像表面上那么简单

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 相对于游标查询,流式对数据库的影响时间要更长一些

  3. 另外流式查询依赖网络,导致网络拥塞可能性较大

流式游标内存分析

表数据量:500w

内存查看工具:JDK 自带 Jvisualvm

设置 JVM 参数:-Xmx512m -Xms512m

单次调用内存使用

流式查询内存性能报告如下

MyBatis中怎么查询千万数据量

图1 数据仅供参考

游标查询内存性能报告如下

MyBatis中怎么查询千万数据量

图2 数据仅供参考

根据内存占用情况来看,游标查询和流式查询都 能够很好的防止 OOM

并发调用内存使用

并发调用:Jmete 1 秒 10 个线程并发调用

流式查询内存性能报告如下

MyBatis中怎么查询千万数据量

图3 数据仅供参考

并发调用对于内存占用情况也很 OK,不存在叠加式增加

流式查询并发调用时间平均消耗:≈ 55s

游标查询内存性能报告如下

MyBatis中怎么查询千万数据量

图4 数据仅供参考

游标查询并发调用时间平均消耗:≈ 83s

因为设备限制,以及部分情况只会在极端下产生,所以没有进行生产、测试多环境验证,小伙伴感兴趣可以自行测试

MyBatis 如何使用流式查询

上文都是在描述如何使用 JDBC 原生 API 进行查询,ORM 框架 Mybatis 也针对流式查询进行了封装

ResultHandler 接口只包含 handleResult 方法,可以获取到已转换后的 Java 实体类

@Slf4j @Service public class MyBatisStreamService {     @Resource     private MyBatisStreamMapper myBatisStreamMapper;      public void mybatisStreamQuery() {         long start = System.currentTimeMillis();         myBatisStreamMapper.mybatisStreamQuery(new ResultHandler<YOU_TABLE_DO>() {             @Override             public void handleResult(ResultContext<? extends YOU_TABLE_DO> resultContext) { }         });         log.info("  ??? MyBatis查询耗时 :: {} ", System.currentTimeMillis() - start);     } }

除了下述注解式的应用方式,也可以使用 .xml 文件的形式

@Mapper public interface MyBatisStreamMapper {     @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)     @ResultType(YOU_TABLE_DO.class)     @Select("SELECT COLUMN_A, COLUMN_B, COLUMN_C FROM YOU_TABLE")     void mybatisStreamQuery(ResultHandler<YOU_TABLE_DO> handler); }

Mybatis 流式查询调用时间消耗:&asymp; 18s

  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. JDBC 流式与 MyBatis 封装的流式读取对比

  3. MyBatis 相对于原生的流式还是慢上了不少,但是考虑到底层的封装的特性,这点性能还是可以接受的

  4. 从内存占比而言,两者波动相差无几

MyBatis 相对于原生 JDBC 更为的方便,因为封装了回调函数以及序列化对象等特性

上述就是小编为大家分享的MyBatis中怎么查询千万数据量了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

MyBatis中怎么查询千万数据量

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

oracle千万级数据怎么查询

查询千万级数据可以采用以下方法:1. 使用索引:对于经常需要查询的字段,可以创建索引来加快查询速度。索引可以提高查询性能,但会增加写入数据的成本。在设计表结构时,可以根据查询需求选择创建合适的索引。2. 分页查询:对于大量数据的查询,可以采
2023-09-13

sqlserver千万级数据怎么查询

在处理千万级数据时,查询性能是非常重要的。以下是一些提高SQL Server查询性能的方法:创建索引:为经常使用的列创建索引,可以大大提高查询性能。确保索引是正确的,以便数据库引擎可以有效地使用它们。使用适当的查询语句:编写优化的查询语句,
sqlserver千万级数据怎么查询
2024-05-14

mysql怎么批量更新千万数据

批量更新千万条数据可以使用MySQL的UPDATE语句结合WHERE子句来实现。下面是一个示例:UPDATE 表名 SET 字段1=值1, 字段2=值2, ... WHERE 条件;你可以根据具体的需求替换表名、字段和条件。要批量更新千
2023-10-24

详解MySQL数据库千万级数据查询和存储

目录百万级数据处理方案数据存储结构设计查询语句优化千万级数据处理方案数据存储结构设计数据库表主键设计MySQL面试题MySQL数据库千万级数据查询优化方案你用过MySQL那些存储引擎他们都有什么特点和区别?那他们都有什么特点和区别呢?MyI
2022-05-14

mysql单表千万级数据查询的方法是什么

在处理千万级数据的单表查询时,可以采取以下方法来提高查询效率:使用合适的索引:在主键字段和经常用于查询的字段上创建索引,可以加快查询速度。但是要注意索引也会占用存储空间,并且会增加更新和插入操作的时间。避免全表扫描:尽量避免使用没有索引的字
mysql单表千万级数据查询的方法是什么
2024-03-02

mybatis怎么批量更新几千条数据

在MyBatis中,可以使用批量更新操作来更新几千条数据。以下是一种实现方式:在Mapper接口中定义一个批量更新的方法,如下所示:public interface UserMapper {void updateBatch(List
mybatis怎么批量更新几千条数据
2024-03-04

MySQL数据库千万级数据查询和存储的示例分析

这篇文章主要介绍MySQL数据库千万级数据查询和存储的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!百万级数据处理方案数据存储结构设计表字段设计表字段 not null,因为 null 值很难查询优化且占用额
2023-06-15

MySQL中怎么优化千万级数据表

MySQL中怎么优化千万级数据表,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。我这里有张表,数据有1000w,目前只有一个主键索引CREATE TABLE `u
2023-06-20

在一个千万级的数据库查寻中,如何提高查询效率????

摸清数据产生量如何,如果是1钞钟1条记录,则一台车一天就有86400条记录,则建议如下:1、每台车使用单独的表,程序内部使用CreateTable,动态创建表,销毁表。这样车与车之间不会产生联系。前提:系统管理的车应该不会经常变来变去,没有很多关联查询出多台车
在一个千万级的数据库查寻中,如何提高查询效率????
2014-10-15

springbatch怎么处理千万级数据

处理千万级数据的方法可以通过以下步骤实现:1. 分批读取数据:使用Spring Batch的chunk机制,将数据分批读取到内存中。可以通过配置ItemReader的pageSize参数来控制每批读取的数据量。2. 多线程处理数据:可以通过
2023-08-18

java怎么处理千万级数据

处理千万级数据可以使用以下方法:1. 使用合适的数据结构:选择合适的数据结构来存储和处理数据,例如使用ArrayList、LinkedList、HashMap等。根据具体的需求和数据特点,选择最适合的数据结构可以提高数据处理的效率。2. 分
java怎么处理千万级数据
2024-02-29

编程热搜

目录