安徽省工程建设信息网站,广州外贸企业网站建设,大型网站开发合同,人社网站行风建设的建设和意见文章目录 背景常规查询流式查询MyBatis 流式查询接口为什么要用流式查询#xff1f; 游标查询OptionsResultType注意#xff1a;原因#xff1a; 非流式查询和流式查询区别#xff1a; 背景
大数据量操作的场景大致如下#xff1a;
数据迁移数据导出批量处理数据
在实际… 文章目录 背景常规查询流式查询MyBatis 流式查询接口为什么要用流式查询 游标查询OptionsResultType注意原因 非流式查询和流式查询区别 背景
大数据量操作的场景大致如下
数据迁移数据导出批量处理数据
在实际工作中当指定查询数据过大时我们一般使用分页查询的方式一页一页的将数据放到内存处理。但有些情况不需要分页的方式查询数据或分很大一页查询数据时如果一下子将数据全部加载出来到内存中很可能会发生OOM(内存溢出)而且查询会很慢因为框架耗费大量的时间和内存去把数据库查询的结果封装成我们想要的对象实体类。
举例在业务系统需要从 MySQL 数据库里读取 100w 数据行进行处理应该怎么做
做法通常如下
常规查询 一次性读取 100w 数据到 JVM 内存中或者分页读取流式查询 建立长连接利用服务端游标每次读取一条加载到 JVM 内存多次获取一次一行游标查询 和流式一样通过 fetchSize 参数控制一次读取多少条数据多次获取一次多行
常规查询
默认情况下完整的检索结果集会将其存储在内存中。在大多数情况下这是最有效的操作方式并且由于 MySQL 网络协议的设计因此更易于实现。
举例假设单表 100w 数据量一般会采用分页的方式查询
Mapper
public interface BigDataSearchMapper extends BaseMapperBigDataSearchEntity {Select(SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} )PageBigDataSearchEntity pageList(Param(page) PageBigDataSearchEntity page, Param(Constants.WRAPPER) QueryWrapperBigDataSearchEntity queryWrapper);}注该示例使用的 MybatisPlus。 该方式比较简单如果在不考虑 LIMIT 深分页优化情况下估计你的数据库服务器就噶皮了或者你能等上几十分钟或几小时甚至几天时间检索数据。
流式查询
流式查询指的是查询成功后不是返回一个集合而是返回一个迭代器应用每次从迭代器取一条查询结果。流式查询的好处是能够降低内存使用。
如果没有流式查询我们想要从数据库取 100w 条记录而又没有足够的内存时就不得不分页查询而分页查询效率取决于表设计如果设计的不好就无法执行高效的分页查询。因此流式查询是一个数据库访问框架必须具备的功能。
MyBatis 中使用流式查询避免数据量过大导致 OOM 但在流式查询的过程当中数据库连接是保持打开状态的因此要注意的是
执行一个流式查询后数据库访问框架就不负责关闭数据库连接了需要应用在取完数据后自己关闭。必须先读取或关闭结果集中的所有行然后才能对连接发出任何其他查询否则将引发异常。
MyBatis 流式查询接口
MyBatis 提供了一个叫 org.apache.ibatis.cursor.Cursor 的接口类用于流式查询这个接口继承了 java.io.Closeable 和 java.lang.Iterable 接口由此可知 Cursor 是可关闭的 Cursor 是可遍历的。 除此之外Cursor 还提供了三个方法 isOpen() 用于在取数据之前判断 Cursor 对象是否是打开状态。只有当打开时 Cursor 才能取数据 isConsumed() 用于判断查询结果是否全部取完。 getCurrentIndex() 返回已经获取了多少条数据
使用流式查询则要保持对产生结果集的语句所引用的表的并发访问因为其查询会独占连接所以必须尽快处理。
为什么要用流式查询
如果有一个很大的查询结果需要遍历处理又不想一次性将结果集装入客户端内存就可以考虑使用流式查询
分库分表场景下单个表的查询结果集虽然不大但如果某个查询跨了多个库多个表又要做结果集的合并、排序等动作依然有可能撑爆内存详细研究了sharding-sphere的代码不难发现除了group by与order by字段不一样之外其他的场景都非常适合使用流式查询可以最大限度的降低对客户端内存的消耗。
游标查询
对大量数据进行处理时为防止内存泄漏情况发生也可以采用游标方式进行数据查询处理。这种处理方式比常规查询要快很多。
当查询百万级的数据的时候还可以使用游标方式进行数据查询处理不仅可以节省内存的消耗而且还不需要一次性取出所有数据可以进行逐条处理或逐条取出部分批量处理。一次查询指定 fetchSize 的数据直到把数据全部处理完。
Mybatis 的处理加了两个注解Options 和 ResultType
Mapper
public interface BigDataSearchMapper extends BaseMapperBigDataSearchEntity {// 方式一 多次获取一次多行Select(SELECT bds.* FROM big_data_search bds ${ew.customSqlSegment} )Options(resultSetType ResultSetType.FORWARD_ONLY, fetchSize 1000000)PageBigDataSearchEntity pageList(Param(page) PageBigDataSearchEntity page, Param(Constants.WRAPPER) QueryWrapperBigDataSearchEntity 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) QueryWrapperBigDataSearchEntity queryWrapper, ResultHandlerBigDataSearchEntity 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();