当前位置: 首页 > news >正文

插入一百万数据的最优解分析和耗时

很多业务情况会有导入功能,可能是提交一批数据进行插入,所以现在需要用一种可行性高的读取以及插入的实现方案。

本文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<String> 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<String> 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<String> 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<String> 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<String> list) {
		List<BigTable> 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和mybatis-plus的执行效果对比

 

 因为有容器的加载,所以jdbc的耗时比main方法的耗时会多个一两秒。

接口请求的情况下,原生jdbc平均耗时16秒,mybatis-plus平均耗时23秒。

相差百分之三四十!你问我选哪种,撸码肯定还是mybatis-plus的。性能差距在可接受的范围之内。

总结

  1. 要开启rewriteBatchedStatements=true,使得数据集可以大批提交
  2. 当每条sql的数据量很大时,根据实际数据量的情况,考虑是减少单条sql数据量,还是增大max_allowed_packet单次允许提交的数据量,一般是前者
  3. 使用BufferReader去操作文件读取会非常快,毫秒级的处理响应

参考文章:

集成mybatis-plus:SpringBoot集成Mybatis-Plus - 腾讯云开发者社区-腾讯云

相关文章:

  • DockerFile的基本知识及利用DockerFile构建镜像
  • Spring(二)
  • 计算机毕业设计ssm+vue基本微信小程序的执法助手平台
  • Java项目--网页版音乐播放器(JQuery前端逻辑)
  • windows service 服务器安装 MySQL
  • springboot+mybatis+mysql+Quartz实现任务调度(定时任务,实现可配置)
  • python简介常考面试题目:python是什么,有什么好处,python2和python3的主要区别
  • SpringCloud Stream消息驱动
  • JVisualVM 中线程状态(运行/休眠/等待/驻留/监视)解析
  • 常识——绳结打折法
  • AVL树的特性和模拟实现
  • java剧院售票系统计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
  • SpringBoot-36-分布式理论概述
  • 第一章 Linux及Linux Shell简介
  • http客户端Feign
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • Apache的80端口被占用以及访问时报错403
  • JAVA_NIO系列——Channel和Buffer详解
  • learning koa2.x
  • node入门
  • tensorflow学习笔记3——MNIST应用篇
  • vue--为什么data属性必须是一个函数
  • Yeoman_Bower_Grunt
  • 阿里云购买磁盘后挂载
  • 分享自己折腾多时的一套 vue 组件 --we-vue
  • 将回调地狱按在地上摩擦的Promise
  • 区块链技术特点之去中心化特性
  • 让你成为前端,后端或全栈开发程序员的进阶指南,一门学到老的技术
  • 使用Gradle第一次构建Java程序
  • 树莓派 - 使用须知
  • 推荐一个React的管理后台框架
  • 为视图添加丝滑的水波纹
  • 学习笔记TF060:图像语音结合,看图说话
  • MPAndroidChart 教程:Y轴 YAxis
  • #define、const、typedef的差别
  • #vue3 实现前端下载excel文件模板功能
  • (C语言)fread与fwrite详解
  • (Redis使用系列) Springboot 实现Redis消息的订阅与分布 四
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)ssm旅游企业财务管理系统 毕业设计 102100
  • (十一)手动添加用户和文件的特殊权限
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • (转)fock函数详解
  • (转)shell中括号的特殊用法 linux if多条件判断
  • (转)人的集合论——移山之道
  • .FileZilla的使用和主动模式被动模式介绍
  • .Net CF下精确的计时器
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化
  • @hook扩展分析
  • @JsonFormat与@DateTimeFormat注解的使用
  • @RestController注解的使用
  • [ vulhub漏洞复现篇 ] Apache Flink目录遍历(CVE-2020-17519)