很多业务情况会有导入功能,可能是提交一批数据进行插入,所以现在需要用一种可行性高的读取以及插入的实现方案。
本文gitee项目地址:BigDataImportProject: 从文件读取大数据量,并写入到数据库中
机器:Cpu是Amd1500x,内存16G,固态硬盘
使用WriteTxtDataService类去生成一百万的数据,创建文件后,使用FileOutputStream写入到文件中。
private static void writeData() throws IOException {
try (FileOutputStream fos = new FileOutputStream(Constant.getFilePath())) {
byte[] c = new byte[2];
c[0] = 0x0d;
c[1] = 0x0a;
String value = new String(c);
int a = Constant.allNum;
Random random = new Random();
while (a > 0) {
long str = random.nextLong();
fos.write((str + value).getBytes());
a--;
}
}
}
将所需的数据集分批读取,这里采用了BufferReader去读取文件,这个读取的过程非常快,因为这个jdk下的包,直接操作字节数据的,从跳跃万行数据到写入仅需十多毫秒,看机器情况。
(前提:文件内容不会发生改变,是只读文件)
步骤如下:
①指定当前批次batchNum,每批次所读取的数据量大小batchCount。
②根据这两个值,跳跃到需读取的行
②读取batchCount行后,写入到list中
private static List getListByBatch(int batchNum) throws IOException {
Integer batchCount = Constant.batchCount;
File file = new File(Constant.getFilePath());
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
int currentNode = (batchNum - 1) * batchCount;
// 移动到所要读取的行
for (int i = 0; i < currentNode; i++) {
bufferedReader.readLine();
}
int count = 0;
String s = null;
List list = new LinkedList<>();
// 读取批次
while ((s = bufferedReader.readLine()) != null && count < batchCount) {
count++;
list.add(s);
}
return list;
}
这一步非常重要,rewriteBatchedStatements=true,可以让MySQL接收批量提交的sql,这样我们在进行事务的批量提交时,就能够一次性把sql发送给MySQL。否则就是一条条发送给MySQL,效率极低。
Connection conn =
DriverManager.getConnection("jdbc:mysql://localhost:3306/" +
Constant.databaseName + "?rewriteBatchedStatements=true",
Constant.userName,
Constant.passWord);
获取到构建好的数据集,关闭事务的自动提交,并且添加多批数据,一次性提交事务给MySQL,结合前面的rewriteBatchedStatements,这样MySQL就能够一次性处理大批数量的sql语句。
public static void saveInfoByBatch(List list) {
PreparedStatement pstmt = null;
try {
conn.setAutoCommit(false);
String sql = "insert into " + Constant.tableName + " values (?)";
pstmt = MySqlOptUtil.getPreparedStmt(conn, sql);
for (String aList : list) {
pstmt.setString(1, aList);
// 添加数据
pstmt.addBatch();
}
// 执行事务
pstmt.executeBatch();
conn.commit();
} catch (SQLException e) {
e.printStackTrace();
MySqlOptUtil.closeStmt(pstmt);
}
}
有朋友就好奇了,我组装一条大sql去插入会怎么样?
前提是要将MySQL的max_allowed_packet设置的很大,默认是1MB的,我改成了100MB,实际业务场景传输的数据量的包大小可能会更大~
这里也有实现方式,只不过组装数据的时候比较麻烦,但是设置一批10000条数据去提交的情况下,批量提交和组装大sql的提交,效率是差不多的,以下是实现代码:
public static void saveInfo(List list) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = MySqlOptUtil.getConn();
StringBuilder builder = new StringBuilder();
builder.append("insert into ").append(Constant.tableName).append(" (big_value) values ");
list.forEach(t -> builder.append("('").append(t).append("'),"));
String sql = builder.toString();
sql = sql.substring(0, sql.length() - 1);
pstmt = MySqlOptUtil.getPreparedStmt(conn, sql);
pstmt.executeUpdate();
} catch (SQLException e) {
log.error("执行失败", e);
} finally {
MySqlOptUtil.closeStmt(pstmt);
MySqlOptUtil.closeConn(conn);
}
}
还有的大兄弟会说,我们业务都是用mybatis的呀,哪有人用jdbc,所以这里我也引入了mybatis-pus的依赖,做了插入,批量条数设置为集合的长度了,因为默认的一千条太慢了。
private static void saveInfoByMybatisPlus(List list) {
List tableList = list.stream().map(t -> new BigTable().setBigValue(t)).collect(Collectors.toList());
BigTableSerive bigTableSerive = SpringUtil.getBean(BigTableSerive.class);
bigTableSerive.saveBatch(tableList, tableList.size());
}
当然rewriteBatchedStatements也是要开启的
实际的打印结果显示,mybatis-plus是组装成一条大sql去提交数据的,所以和分一条条sql的性能没什么大的区别,(通过上述分点得知)。
单线程:
多线程:
由此可见,单线程和多线程的效率相差不大。
因为有容器的加载,所以jdbc的耗时比main方法的耗时会多个一两秒。
接口请求的情况下,原生jdbc平均耗时16秒,mybatis-plus平均耗时23秒。
相差百分之三四十!你问我选哪种,撸码肯定还是mybatis-plus的。性能差距在可接受的范围之内。
参考文章:
集成mybatis-plus:SpringBoot集成Mybatis-Plus - 腾讯云开发者社区-腾讯云