MySQL 流处理和游标处理

277 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情

MySQL 流处理和游标处理

前言

在开发过程中, 经常会遇到批处理的需求, 数据量比较少的时候, 可以直接将所有数据加载到内存中进行处理, 但是数据量多的话, 就会造成OOM了, 这种处理方式属于ResultsetRowsStatic静态结果集处理, 也是MySQL默认的处理方式, 我们这里可以采取分页的方式来避免OOM, 但是数据量大的深度分页的还是让人难以接受的, 其实MySQL还提供了更丰富的处理方式, 如下

image.png

ResultsetRowsStreaming(流处理)

image.png

我们以Mybatis使用为例, 这里使用注解方式, 如果需要用到Mybatis的动态sql, 需要使用<script> </script>包裹代码

// Mapper代码
@Select({"<script>", " select * from tb_book where  type = #{type}", "</script>"})
@Options(fetchSize = Integer.MIN_VALUE)
@ResultType(TbBook.class)
void streamBatch(@Param("type") Integer type, ResultHandler<TbBook> batchBook);
// Service处理代码
@Override
public void streamBatch(Integer type) {
    baseMapper.streamBatch(type, book -> System.out.println("处理数据: " + book.toString()));
}

我们在ResultsetRowsStreaming的构造函数中加上断点调试, 然后执行streamBatch方法, 可以看到断点成功走到ResultsetRowsStreaming这里

image.png

ResultsetRowsCursor(游标处理)

与流不同的是游标是每次返回fetchSize大小数据处理

image.png

注意这里需要加@Transactional来维持会话, 不然数据没处理完会话关闭就报错了

// Mapper代码
Cursor<TbBook> cursorBatch(@Param("type") Integer type);
// Service代码
@Override
@Transactional
public void cursorBatch(Integer type) throws IOException {
    try(Cursor<TbBook> tbBooks = baseMapper.cursorBatch(type)){
        tbBooks.forEach(book -> System.out.println("处理数据: " + book.toString()));
    }
}
<!--  Mapper xml代码 -->
<select id="cursorBatch" resultType="TbBook" fetchSize="10">
  select * from tb_book where  type = #{type}
</select>

同样的我们ResultsetRowsCursor的构造函数中加上断点调试, 然后执行cursorBatch方法, 可以看到断点成功走到ResultsetRowsCursor这里

image.png

相关条件

可以看到使用流/游标是有条件的, 其中type默认就是Type.FORWARD_ONLY, concurrency默认就是CONCUR_READ_ONLY, 也就是使用流只需再满足fetchSize = Integer.MIN_VALUE就行了(这里源码中写死的一条一条处理), 使用游标还需要useCursorFetch=true(这个是配置在datasource url的参数)和fetchSize > 0即可

image.png

image.png

使用注意: 相比静态结果集方式, 这种动态的处理方式虽然会降低应用程序的内存占用, 但是会长时间的占用数据库连接, 应该根据自己的业务场景灵活选择使用方式