MySQL大数据量查询并写入文件优化 | 游标Cursor

测试环境

MySQL8.0、JDK8、JVM内存2G,单表全量数据157W

使用JVisualVM连接上应用程序:

全量查询

使用select * 全量查询,将结果保存到List中并写入文件

@Override
public void getStudent() {
    long start = System.currentTimeMillis();
    List<StudentEntity> scan = this.list(); // Mybatis-plus全量查询
    long mid = System.currentTimeMillis();
    log.info("查询耗时:[{}]ms", (mid - start));
    // 指定文件路径
    String filePath = "D:\\students2.txt";
    try {
        // 创建文件对象
        File file = new File(filePath);
        // 创建文件的父目录(如果不存在)
        file.getParentFile().mkdirs();
        // 创建文件
        file.createNewFile();
        // 创建文件写入器
        FileWriter writer = new FileWriter(file);
        // 逐行写入数据
        for (StudentEntity student : scan) {
            writer.write(student.toString());
            writer.write(System.lineSeparator()); // 换行
        }
        // 关闭写入器
        writer.close();
        System.out.println("文件写入成功");
    } catch (IOException e) {
    }
    long end = System.currentTimeMillis();
    log.info("写入文件耗时:[{}]ms", (end - mid));
}

耗时:

内存使用情况:1200M左右

游标查询优化

  • 使用Cursor进行增量查询,并流式写入文件中
@Override
public void getStudentByCursor() {
    long start = System.currentTimeMillis();
    Cursor<StudentEntity> scan = baseMapper.scan(); // 游标查询
    long mid = System.currentTimeMillis();
    log.info("查询耗时:[{}]ms", (mid - start));
    // 指定文件路径
    String filePath = "D:\\students.txt";
    try {
        // 创建文件对象
        File file = new File(filePath);
        // 创建文件的父目录(如果不存在)
        file.getParentFile().mkdirs();
        // 创建文件
        file.createNewFile();
        // 创建文件写入器
        FileWriter writer = new FileWriter(file);
        // 逐行写入数据
        for (StudentEntity student : scan) {
            writer.write(student.toString());
            writer.write(System.lineSeparator()); // 换行
        }
        // 关闭写入器
        writer.close();
        System.out.println("文件写入成功");
    } catch (IOException e) {
    }
    long end = System.currentTimeMillis();
    log.info("写入耗时:[{}]ms", (end - mid));
}

变化在于baseMapper.scan()方法,返回值为Cursor<?>:

@Select("select * from student")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = Integer.MIN_VALUE)
@ResultType(StudentEntity.class)
Cursor<StudentEntity> scan();

resultSetType = ResultSetType.FORWARD_ONLY表示游标只能向后移动,在写入文件场景下适用。

  • Mybatis的Cursor类:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.apache.ibatis.cursor;
import java.io.Closeable;
public interface Cursor<T> extends Closeable, Iterable<T> {
    boolean isOpen();
    boolean isConsumed();
    int getCurrentIndex();
}

耗时:

内存使用情况:660M左右

总结

游标查询极大加快了查询的耗时,但写入文件时由于需要不断的移动游标指针,导致写入文件的时间变长。上述例子中总耗时基本相同。

但如果实际生产中,数据量更大、需要导出的数据表更多时,全量导出很容易达到JVM内存最大值,从而导致查询速度很慢,甚至报OOM错误;

而游标查询可以很大程度上避免OOM,执行效率对比全量查询,随着数据量的增大而提高;

在上述例子中游标查询内存使用高峰值为660M,而全量查询为1200M,节约了50%的内存的使用空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值