厉害了!12秒将百万数据通过EasyExcel导入MySQL数据库中

一、写在开头

我们在上一篇文章中提到了通过EasyExcel处理Mysql百万数据的导入功能(一键看原文),当时我们经过测试数据的反复测验,100万条放在excel中的数据,4个字段的情况下,导入数据库,平均耗时500秒,这对于我们来说肯定难以接受,今天我们就来做一次性能优化。

在这里插入图片描述


二、性能瓶颈分析

一般的大数据量excel入库的场景中,耗时大概在如下几点里:

  • 耗时1: 百万数据读取,字段数量,sheet页个数,文件体积;针对这种情况,我们要选择分片读取,选择合适的集合存储。
  • 耗时2: 百万数据的校验,逐行分字段校验;这种情况的耗时会随着字段个数逐渐增加,目前我们的案例中不设计,暂不展开。
  • 耗时3: 百万数据的写入;选择合适的写入方式,如Mybatis-plus的分批插入,采用多线程处理等。

三、针对耗时1进行优化

耗时2的场景我们在案例中并未用到,耗时1中针对百万级数据的读取,我们必然要选择分片读取,分片处理,这在我们上一篇文章中就已经采用了该方案,这里通过实现EasyExcel的ReadListener页面读取监听器,实现其invoke方法,在方法中我们增加BATCH_COUNT(单次读取条数)配置,来进行分片读取。读取完后,我们一定要选择合适的集合容器存放临时数据,不同集合之间的增加数据性能存在差异这里我们选择ArrayList。

【优化前代码片段】

@Slf4j
@Service
public class EasyExcelImportHandler implements ReadListener<User> {/*成功数据*/private final CopyOnWriteArrayList<User> successList = new CopyOnWriteArrayList<>();/*单次处理条数*/private final static int BATCH_COUNT = 20000;@Resourceprivate ThreadPoolExecutor threadPoolExecutor;@Resourceprivate UserMapper userMapper;@Overridepublic void invoke(User user, AnalysisContext analysisContext) {if(StringUtils.isNotBlank(user.getName())){successList.add(user);return;}if(successList.size() >= BATCH_COUNT){log.info("读取数据:{}", successList.size());saveData();}}//////
}

【优化后代码片段】

@Slf4j
@Service
public class EasyExcelImportHandler implements ReadListener<User> {/*成功数据*/// private final CopyOnWriteArrayList<User> successList = new CopyOnWriteArrayList<>();private final List<User> successList =  new ArrayList<>();/*单次处理条数,有原来2万变为10万*/private final static int BATCH_COUNT = 100000;@Resourceprivate ThreadPoolExecutor threadPoolExecutor;@Resourceprivate UserMapper userMapper;@Overridepublic void invoke(User user, AnalysisContext analysisContext) {if (StringUtils.isNotBlank(user.getName())) {successList.add(user);return;}//size是否为100000条:这里其实就是分批.当数据等于10w的时候执行一次插入if (successList.size() >= BATCH_COUNT) {log.info("读取数据:{}", successList.size());saveData();//清理集合便于GC回收successList.clear();}}//////}

这里面我们主要做了2点优化,1)将原来的线程安全的CopyOnWriteArrayList换为ArrayList,前者虽然可保线程安全,但存储数据性能很差;2)将原来单批次2000调整为100000,这个参数是因电脑而异的,并没有最佳数值。

【注】本文中的代码仅针对优化点贴出,完整代码参考文首中的上一篇文章连接哈!


四、针对耗时3进行优化

针对耗时3的处理方案,我们这里准备了2个:JDBC分批插入+手动事务控制多线程+Mybatis-Plus批量插入

4.1 JDBC分批插入+手动事务控制

很多博文中都说mybatis批量插入性能低,有人建议使用原生的JDBC进行处理,那咱们就采用这种方案来测试一下。

首先我们既然要通过jdbc连接数据库进行操作,那就先准备一个连接工具类吧

public class JdbcConnectUtil {private static  String driver;private static  String url;private static  String name;private static  String password;/*** 创建数据Properties集合对象加载加载配置文件*/static {Properties properties = new Properties();try {properties.load(JdbcConnectUtil.class.getClassLoader().getResourceAsStream("generator.properties"));driver = properties.getProperty("jdbc.driverClass");url = properties.getProperty("jdbc.connectionURL");name = properties.getProperty("jdbc.userId");password = properties.getProperty("jdbc.password");Class.forName(driver);} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}/*** 获取数据库连接对象* @return* @throws Exception*/public static Connection getConnect() throws Exception {return DriverManager.getConnection(url, name, password);}/*** 关闭数据库相关资源* @param conn* @param ps* @param rs*/public static void close(Connection conn, PreparedStatement ps, ResultSet rs) {try {if (conn != null) conn.close();if (ps != null) ps.close();if (rs != null) rs.close();} catch (SQLException e) {throw new RuntimeException(e);}}public static void close(Connection conn, PreparedStatement ps) {close(conn, ps, null);}public static void close(Connection conn, ResultSet rs) {close(conn, null, rs);}
}

有了工具类后,我们就可以在EasyExcelImportHandler类中进行JDBC导入逻辑的实现啦。

 /*** jdbc+事务处理*/public void import4Jdbc(){//分批读取+JDBC分批插入+手动事务控制Connection conn = null;//JDBC存储过程PreparedStatement ps = null;try {//建立jdbc数据库连接conn = JdbcConnectUtil.getConnect();//关闭事务默认提交conn.setAutoCommit(false);String sql = "insert into user (id,name, phone_num, address) values";sql += "(?,?,?,?)";ps = conn.prepareStatement(sql);for (int i = 0; i < successList.size(); i++) {User user = new User();ps.setInt(1,successList.get(i).getId());ps.setString(2,successList.get(i).getName());ps.setString(3,successList.get(i).getPhoneNum());ps.setString(4,successList.get(i).getAddress());//将一组参数添加到此 PreparedStatement 对象的批处理命令中。ps.addBatch();}//执行批处理ps.executeBatch();//手动提交事务conn.commit();} catch (Exception e) {e.printStackTrace();} finally {//记得关闭连接JdbcConnectUtil.close(conn,ps);}}

这里我们通过PreparedStatement的addBatch()和executeBatch()实现JDBC的分批插入,然后用import4Jdbc()替换原来的savaData()即可。

经过多次导入测试,这种方案的平均耗时为140秒。相比之前的500秒确实有了大幅度提升,但是2分多钟仍然感觉有点慢。
在这里插入图片描述

4.2 多线程+Mybatis-Plus批量插入

我们知道Mybatis-Plus的IService中提供了saveBatch的批量插入方法,但经过查看日志发现Mybatis-Plus的saveBatch在最后还是循环调用的INSERT INTO语句!

这种情况下,测试多线程速度和单线程相差不大,所以需要实现真正的批量插入语句,两种方式,一种是通过给Mybatis-Plus注入器,增强批量插入,一种是在xml文件中自己拼接SQL语句,我们在这里选用后一种,因为我们只做一个表,直接手写xml很方便,如果是在企业开发时建议使用sql注入器实现(自定义SQL注入器实现DefaultSqlInjector,添加InsertBatchSomeColumn方法,通过使用InsertBatchSomeColumn方法批量插入。)。

【XML中手动批量插入】

 <insert id="insertSelective" parameterType="java.util.List">insert into user(id,name, phone_num, address)values<foreach collection="list" item="item" separator=",">(#{item.id},#{item.name},#{item.phoneNum},#{item.address})</foreach></insert>

在在EasyExcelImportHandler类中的saveData()方法中实现多线程批量插入。

/*** 采用多线程读取数据*/private void saveData() {List<List<User>> lists = ListUtil.split(successList, 1000);CountDownLatch countDownLatch = new CountDownLatch(lists.size());for (List<User> list : lists) {threadPoolExecutor.execute(() -> {try {userMapper.insertSelective(list.stream().map(o -> {User user = new User();user.setName(o.getName());user.setId(o.getId());user.setPhoneNum(o.getPhoneNum());user.setAddress(o.getAddress());return user;}).collect(Collectors.toList()));} catch (Exception e) {log.error("启动线程失败,e:{}", e.getMessage(), e);} finally {//执行完一个线程减1,直到执行完countDownLatch.countDown();}});}// 等待所有线程执行完try {countDownLatch.await();} catch (Exception e) {log.error("等待所有线程执行完异常,e:{}", e.getMessage(), e);}// 提前将不再使用的集合清空,释放资源successList.clear();lists.clear();}

经过多次导入测试,100万数据量导入耗时平均在20秒,这就是一个很客观且友好用户的导入功能啦,毕竟100万的xlsx文件,打开都需要七八秒呢!
在这里插入图片描述


五、总结

OK!以上就是SpringBoot项目下,通过阿里开源的EasyExcel技术进行百万级数据的导入功能的优化步骤啦,由原来的500秒优化到20秒!

六、结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

在这里插入图片描述
如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/692225.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

树莓派点亮FPGA小灯

树莓派点亮FPGA小灯 引言&#xff1a; ​ 本次实验的目的是通过树莓派和FPGA之间的串口通信&#xff0c;控制FPGA开发板上的小灯。实验将展示如何使用树莓派发送特定的字符信号&#xff0c;通过串口传输至FPGA&#xff0c;并在FPGA上实现逻辑解析&#xff0c;以点亮指定的小灯。…

[240512] x-cmd 发布 v0.3.6: (se,wkp,ddgo...)x( kimi,gemini,gpt...)

目录 x-cmd 发布 v0.3.6新增了 jina 模块新增了 ddgo 模块新增了 se 模块wkp 模块新增了 writer 模块cosmo 模块 x-cmd 发布 v0.3.6 本次版本的最新引入的功能都是目的为了进一步探索 LLM 的使用。 本版本的改进分为两类&#xff1a;资讯类模块&#xff08;Wikipedia&#xf…

李廉洋:5.12黄金原油下周一行情分析,必看策略。

黄金消息面分析&#xff1a;美国4月份潜在通胀可能出现6个月来的首次放缓&#xff0c;在一系列意外上涨之后&#xff0c;物价压力有望再次开始缓解。核心CPI预计环比上涨0.3%&#xff0c;此前三个月的涨幅均为0.4%&#xff0c;同比预计将上涨3.6%&#xff0c;尽管这一增幅将是三…

rust开发web服务器框架,github排名对比

Rocket Star最多的框架 github仓库地址&#xff1a;GitHub - rwf2/Rocket: A web framework for Rust. Rocket 是一个针对 Rust 的异步 Web 框架&#xff0c;重点关注可用性、安全性、可扩展性和速度。 Axum 异步运行时 githuh仓库地址&#xff1a;GitHub - tokio-rs/axum: …

能源效率:未来可持续发展的全球当务之急

当前全球正面临着严重的能源与气候危机&#xff0c;能源消耗不断增长导致环境污染、气候变化等问题日益严重。在这一背景下&#xff0c;提高能源效率成为了当务之急。今天&#xff0c;我们来简要探讨一下能源效率在全球可持续发展中的重要性&#xff0c;重点关注建筑物能源效率…

C语言——文件相关操作补充

一、文件读取结束的判定 当我们使用例如fgetc、fgets、fscanf、fread等函数来读取文件内容时&#xff0c;我们可能遇到需要判断文件读取的结束&#xff0c;一般情况下都是通过这些函数的返回值来判断文件读取是否结束。 1、fgetc 返回读取的字符的ASCII值&#xff0c;如果读…

微信授权登录02-移动端

目录 ## 前言 1.准备工作 1.1 网站域名 1.2 微信公众号 2.授权登录开发 2.1 前端开发 2.1.1 调起微信授权页面 ## 调起微信授权页面效果图 2.1.2 用户允许授权后回调处理 2.2 后端开发 2.2.1 根据code查询用户信息 2.2.2 自动注册登录 ## 后记 ## 前言 上一篇写…

使用Flask构建POST请求的Web应用

文章目录 准备工作创建路由处理POST请求创建表单页面运行应用结论 在Web开发中&#xff0c;处理POST请求是一项常见任务&#xff0c;特别是在构建表单提交、用户注册和数据提交等功能时。Flask是一个简单而强大的Python Web框架&#xff0c;它提供了方便的工具来处理HTTP请求&a…

bash tab 补全报错 bash: syntax error near unexpected token `(‘

使用 vim 编辑文件时&#xff0c;敲下 vim xxx 后&#xff0c;再键入 tab 键报进行补全报错 bash: syntax error near unexpected token (. 打开 bash 的命令执行详情 set -v 定位到具体的代码&#xff1a; 显然&#xff0c;代码位于 bash 补全的逻辑当中。 定位代码具体的…

大学生体质测试|基于Springboot+vue的大学生体质测试管理系统设计与实现(源码+数据库+文档)

大学生体质测试管理系统 目录 基于Springboot&#xff0b;vue的大学生体质测试管理系统设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3用户功能模块 4教师功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算…

Nios-II编程入门实验

文章目录 一 Verilog实现流水灯二 Nios实现流水灯2.1 创建项目2.2 SOPC添加模块2.3 SOPC输入输出连接2.4 Generate2.5 软件部分2.6 运行结果 三 Verilog实现串口3.1 代码3.2 引脚3.3 效果 四 Nios2实现串口4.1 sopc硬件设计4.2 top文件4.3 软件代码4.4 实现效果 五 参考资料六 …