测试环境
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%的内存的使用空间。