服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

MyBatis Plus 解决大数据量查询慢问题

日期: 来源:石杉的架构笔记收集编辑:旷野历程
「 关注“石杉的架构笔记”,大厂架构经验倾囊相授 

文章来源:https://blog.csdn.net/xhaimail/article/details/119386460


目录
  • 常规查询
  • 流式查询
  • 游标查询


前言


大数据量操作的场景大致如下:

  • 数据迁移
  • 数据导出
  • 批量处理数据

在实际工作中当指定查询数据过大时,我们一般使用分页查询的方式一页一页的将数据放到内存处理。但有些情况不需要分页的方式查询数据或分很大一页查询数据时,如果一下子将数据全部加载出来到内存中,很可能会发生OOM(内存溢出);而且查询会很慢,因为框架耗费大量的时间和内存去把数据库查询的结果封装成我们想要的对象(实体类)。

举例:在业务系统需要从 MySQL 数据库里读取 100w 数据行进行处理,应该怎么做?

做法通常如下:

  • 常规查询: 一次性读取 100w 数据到 JVM 内存中,或者分页读取
  • 流式查询: 建立长连接,利用服务端游标,每次读取一条加载到 JVM 内存(多次获取,一次一行)
  • 游标查询: 和流式一样,通过 fetchSize 参数,控制一次读取多少条数据(多次获取,一次多行)


常规查询





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

举例:

假设单表 100w 数据量,一般会采用分页的方式查询:

@Mapper
public interface BigDataSearchMapper extends BaseMapper<BigDataSearchEntity> {
 
    @Select("SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} ")
    Page<BigDataSearchEntity> pageList(@Param("page") Page<BigDataSearchEntity> page, @Param(Constants.WRAPPER) QueryWrapper<BigDataSearchEntity> queryWrapper);
 
}

注:该示例使用的 MybatisPlus

该方式比较简单,如果在不考虑 LIMIT 深分页优化情况下,估计你的数据库服务器就噶皮了,或者你能等上几十分钟或几小时,甚至几天时间检索数据


流式查询





流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器,应用每次从迭代器取一条查询结果。流式查询的好处是能够降低内存使用。

如果没有流式查询,我们想要从数据库取 100w 条记录而又没有足够的内存时,就不得不分页查询,而分页查询效率取决于表设计,如果设计的不好,就无法执行高效的分页查询。因此流式查询是一个数据库访问框架必须具备的功能。

MyBatis 中使用流式查询避免数据量过大导致 OOM ,但在流式查询的过程当中,数据库连接是保持打开状态的,因此要注意的是:

  • 执行一个流式查询后,数据库访问框架就不负责关闭数据库连接了,需要应用在取完数据后自己关闭。
  • 必须先读取(或关闭)结果集中的所有行,然后才能对连接发出任何其他查询,否则将引发异常。


MyBatis 流式查询接口


MyBatis 提供了一个叫 org.apache.ibatis.cursor.Cursor 的接口类用于流式查询,这个接口继承了 java.io.Closeablejava.lang.Iterable 接口,由此可知:

  • Cursor 是可关闭的;
  • Cursor 是可遍历的。

除此之外,Cursor 还提供了三个方法:

  • isOpen(): 用于在取数据之前判断 Cursor 对象是否是打开状态。只有当打开时 Cursor 才能取数据;
  • isConsumed(): 用于判断查询结果是否全部取完。
  • getCurrentIndex(): 返回已经获取了多少条数据

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


为什么要用流式查询?


如果有一个很大的查询结果需要遍历处理,又不想一次性将结果集装入客户端内存,就可以考虑使用流式查询;

分库分表场景下,单个表的查询结果集虽然不大,但如果某个查询跨了多个库多个表,又要做结果集的合并、排序等动作,依然有可能撑爆内存;详细研究了sharding-sphere的代码不难发现,除了group byorder by字段不一样之外,其他的场景都非常适合使用流式查询,可以最大限度的降低对客户端内存的消耗。



游标查询





对大量数据进行处理时,为防止内存泄漏情况发生,也可以采用游标方式进行数据查询处理。这种处理方式比常规查询要快很多。

当查询百万级的数据的时候,还可以使用游标方式进行数据查询处理,不仅可以节省内存的消耗,而且还不需要一次性取出所有数据,可以进行逐条处理或逐条取出部分批量处理。一次查询指定 fetchSize 的数据,直到把数据全部处理完。

Mybatis 的处理加了两个注解:@Options@ResultType

@Mapper
public interface BigDataSearchMapper extends BaseMapper<BigDataSearchEntity> {
 
    // 方式一 多次获取,一次多行
    @Select("SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} ")
    @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000000)
    Page<BigDataSearchEntity> pageList(@Param("page") Page<BigDataSearchEntity> page, @Param(Constants.WRAPPER) QueryWrapper<BigDataSearchEntity> queryWrapper);
 
    // 方式二 一次获取,一次一行
    @Select("SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} ")
    @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 100000)
    @ResultType(BigDataSearchEntity.class)
    void listData(@Param(Constants.WRAPPER) QueryWrapper<BigDataSearchEntity> queryWrapper, ResultHandler<BigDataSearchEntity> handler);
 
}

@Options

  • ResultSet.FORWORD_ONLY:结果集的游标只能向下滚动
  • ResultSet.SCROLL_INSENSITIVE:结果集的游标可以上下移动,当数据库变化时,当前结果集不变
  • ResultSet.SCROLL_SENSITIVE:返回可滚动的结果集,当数据库变化时,当前结果集同步改变
  • fetchSize:每次获取量

@ResultType

  • @ResultType(BigDataSearchEntity.class):转换成返回实体类型

注意:返回类型必须为 void ,因为查询的结果在 ResultHandler 里处理数据,所以这个 hander 也是必须的,可以使用 lambda 实现一个依次处理逻辑。

注意:

虽然上面的代码中都有 @Options 但实际操作却有不同:

  • 方式一是多次查询,一次返回多条;
  • 方式二是一次查询,一次返回一条;

原因:

Oracle 是从服务器一次取出 fetch size 条记录放在客户端,客户端处理完成一个批次后再向服务器取下一个批次,直到所有数据处理完成。

MySQL 是在执行 ResultSet.next() 方法时,会通过数据库连接一条一条的返回。flush buffer 的过程是阻塞式的,如果网络中发生了拥塞,send buffer 被填满,会导致 buffer 一直 flush 不出去,那 MySQL 的处理线程会阻塞,从而避免数据把客户端内存撑爆。

非流式查询和流式查询区别:

  • 非流式查询:内存会随着查询记录的增长而近乎直线增长。
  • 流式查询:内存会保持稳定,不会随着记录的增长而增长。其内存大小取决于批处理大小BATCH_SIZE的设置,该尺寸越大,内存会越大。所以BATCH_SIZE应该根据业务情况设置合适的大小。

另外要切记每次处理完一批结果要记得释放存储每批数据的临时容器,即上文中的gxids.clear();

欢迎扫码加入儒猿技术交流群,每天晚上20:00都有Java面试、Redis、MySQL、RocketMQ、SpringCloudAlibaba、Java架构等技术答疑分享,更能跟小伙伴们一起交流技术


另外推荐儒猿课堂的9.9元系列课程给您,欢迎加入一起学习~


互联网Java工程师面试突击课
(9.9元专享)

SpringCloudAlibaba零基础入门到项目实战
9.9元专享)

亿级流量下的电商详情页系统实战项目
9.9元专享)

Kafka消息中间件内核源码精讲
9.9元专享)

12个实战案例带你玩转Java并发编程
9.9元专享)

Elasticsearch零基础入门到精通
9.9元专享)

基于Java手写分布式中间件系统实战
9.9元专享)

基于ShardingSphere的分库分表实战课
9.9元专享)

相关阅读

  • GUI界面的信息搜集工具 -- ThunderSearch

  • 一、工具介绍调用ZoomEye的官方api(未来会支持更多资产搜索引擎),开发的GUI界面的信息搜集工具。支持查询主机搜索、域名/IP、web应用搜索、个人信息,支持读取config.json文件
  • 20%的人掌握80%财富?不 真相更残酷

  • 20%的人掌握80%的财富?不,真相更残酷。 胡润富豪榜都看了对吧。那么,招行财报呢?富豪榜都多少多少亿,普通人没啥感觉。一个亿和一百亿,对我没差别。但看了后者,我不禁悲从中来——
  • 云知光 · 日签 | 你造了月亮来标记季节

  • 云知光·日签总策划 | 曹传双执行策划 | 张晨内容整理 | 张靖晓视觉设计 | 潘重坚、王家昌投稿/合作 | media@elicht.cn在2月的最后一天,咱宣布个事儿「亮话」合集 | 2023年,
  • 公共数据运营,智慧城市建设的社会大考!

  • 智慧城市建设的目的,是用数据说话,用数据决策,用数据治理,用数据服务,最终走向数据驱动的城市。对于一些投入很多建设的智慧城市呢,就说招商吧,政府招商时,没有具体数据,文旅产业招商
  • 2023年中国公共数据运营大会召开。

  • 3月30日,由中国智慧城市论坛主办的2023年中国公共数据运营大会召开。工信部政策法规司原司长、中国智慧城市论坛副主席郭福华出席并做指导致辞,中国科技法学会智慧城市工作委

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四
  • 美国对华2000亿关税清单,到底影响有多大?

  • 1 今天A股大跌,上证最大跌幅超过2%。直接导火索是美国证实计划对华2000亿美元产品加征25%关税。 听起来,2000亿美元数目巨大,我们来算笔账。 2000亿美元,按现在人民币汇率

最新文章

  • MyBatis Plus 解决大数据量查询慢问题

  • 「 关注“石杉的架构笔记”,大厂架构经验倾囊相授 」文章来源:https://blog.csdn.net/xhaimail/article/details/119386460目录常规查询流式查询游标查询前言大数据量操作的场
  • 设计糟糕的分库分表是如何把系统搞挂的~~~

  • 原价399元,现在参加拼团活动立享优惠价仅 109 元,赶快一起参团吧!《海量数据场景下的分库分表生产实战》=== 课程福利 ===为了让更多同学学到赚到,《海量数据场景下的分库分表生
  • 2023/04/28(NO.1330)

  • 每天看穿搭填鸭式变帅123...精选穿搭,每天不止3套扫码秒解锁▼每日力荐好物BONELESS 美式短袖T恤 力荐理由 这款克重255的T恤,走线和做工我个人真的非常满意!摸上去有一种扎实