百万级数据导入导出

news2025/1/10 23:43:26

导入

引入easyexcel

	<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>easyexcel</artifactId>
			<version>3.3.2</version>
	</dependency>

总结有三种导出方式:

  • 单行读取,单行导入。这种方案最简单,最慢。

  • 分页读取,分页导入。这种方案利用MySql批量插入可优化到二三十秒。

  • 多线程分页读取,并导入。这种方案利用MySql innerDB引擎支持并发插入的特点可以进一步优化到10几秒。

数据准备

表格:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gwRABG0u-1693143285363)(C:\Users\Administrator\AppData\Roaming\marktext\images\2023-08-27-20-56-00-image.png)]

表:

CREATE TABLE `test_import_export` (
  `seckill_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `order_id` varchar(51) NOT NULL COMMENT '订单id',
  `user_id` int(20) NOT NULL,
  `goods_id` int(11) NOT NULL,
  PRIMARY KEY (`seckill_id`),
  UNIQUE KEY `uidx_so_oid` (`order_id`) USING BTREE,
  UNIQUE KEY `uidx_so_id` (`seckill_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

单行读取,单行导入

这种方式不做过多讲解,太慢了

      //单行读取
    @Test
    public void importFile() throws InterruptedException, ExecutionException, FileNotFoundException {
        String fileName = "D:\\repeatedWrite1693111137007.xlsx";

        long start = System.currentTimeMillis();

        //单行解析
        // 这里默认每次会读取1条数据 然后返回过来 直接调用使用数据就行
        // 具体需要返回多少行可以在`PageReadListener`的构造函数设置
        List<ReadSheet> sheets = EasyExcelUtils.listSheet(new FileInputStream(fileName));
        for (int i = 0; i < sheets.size(); i++) {
            int finalI = i;
            EasyExcel.read(fileName, TestImportExport.class, new PageReadListener<TestImportExport>(dataList -> {
                log.info("第{}页,读取到{}条数据", dataList.size(), finalI);
                testImportExportMapper.insert(dataList.get(0));
            }, 1)).excelType(ExcelTypeEnum.XLSX).sheet(finalI).doRead();
        }
        log.info("消耗:{}", (System.currentTimeMillis() - start) / 1000);
     }

分页读取,分页导入

 //分页读取 --22s
    @Test
    public void importFile1() throws InterruptedException, ExecutionException, FileNotFoundException {
        String fileName = "D:\\repeatedWrite1693111137007.xlsx";

        long start = System.currentTimeMillis();

        ExecutorService executorService = Executors.newFixedThreadPool(17);
        List<Callable<String>> callables = new ArrayList<>();
        //单行解析
        // 这里默认每次会读取10000条数据 然后返回过来 直接调用使用数据就行
        // 具体需要返回多少行可以在`PageReadListener`的构造函数设置
        List<ReadSheet> sheets = EasyExcelUtils.listSheet(new FileInputStream(fileName));
        for (int i = 0; i < sheets.size(); i++) {
            int finalI = i;
            EasyExcel.read(fileName, TestImportExport.class, new PageReadListener<TestImportExport>(dataList -> {
                log.info("第{}页,读取到{}条数据", dataList.size(), finalI);
                testImportExportMapper.insertBatch(dataList);
            }, 10000)).excelType(ExcelTypeEnum.XLSX).sheet(finalI).doRead();
        }
        log.info("消耗:{}", (System.currentTimeMillis() - start) / 1000);
    }

这种方式利用easyexcel提供的PageReadListener来监听读取到的excel数据,每次读取10000条,并插入数据库。我这里的excel每个sheet有50000条数据,所以每个sheet会分5页。通过EasyExcelUtils.listSheet获取到sheets的数量,接下来就循环去读取每个sheet的数据。

这里测试100万数据花了20秒。excel的格式如下

多线程分页读取,并导入

  //多线程分页读取 --10s
    @Test
    public void importFile2() throws InterruptedException, ExecutionException, FileNotFoundException {
        String fileName = "D:\\repeatedWrite1693111137007.xlsx";

        long start = System.currentTimeMillis();
        
        ExecutorService executorService = Executors.newFixedThreadPool(17);
        List<Callable<String>> callables = new ArrayList<>();
        //单行解析
        // 这里默认每次会读取10000条数据 然后返回过来 直接调用使用数据就行
        // 具体需要返回多少行可以在`PageReadListener`的构造函数设置
        List<ReadSheet> sheets = EasyExcelUtils.listSheet(new FileInputStream(fileName));
        for (int i = 0; i < sheets.size(); i++) {
            int finalI = i;
            //代码1
            Callable callable = (Callable<String>) () -> {
                EasyExcel.read(fileName, TestImportExport.class, new PageReadListener<TestImportExport>(dataList -> {
                    log.info("读取到{}条数据, 第{}页", dataList.size(), finalI);
                    testImportExportMapper.insertBatch(dataList);
                }, 10000)).excelType(ExcelTypeEnum.XLSX).sheet(finalI).doRead();
                return "OK";
            };
            callables.add(callable);
        }
          //代码2
        List<Future<String>> futures = executorService.invokeAll(callables);
        for (Future<String> future : futures) {
            String s = future.get();
            System.out.println(s);
        }

        log.info("消耗:{}", (System.currentTimeMillis() - start) / 1000);
    }

代码1处在上一个方法的基础上将读取和插入数据库的操作放在Callable中,在代码2处统一用线程处理,这里定义的线程数是cpu+1个线程数(CPU密集型)

测试结果为10几秒时间

导出

导出主要在于优化查询语句,一次性查询全部肯定行不通,需要分页查询。

给出两种优化语句的方式:

  • 第一种:利用上次查询到的最大主键id,先过滤掉小于最大主键id的数据,然后取最前面的pageSize条。这种适合连续的分页查询,不适合跳页。速度测试为ms级别
SELECT  seckill_id, order_id, user_id, goods_id FROM test_import_export
        WHERE
        seckill_id > #{idMax}
        limit #{pageSize}
  • 第二种:先查询出需要查询的最开始那条的id,然后过滤掉小于该id的数据,取最前面的pageSize条。这种适合分页查以及加条件都可以,但是查询较慢。测试速度为s级别。

    select seckill_id, order_id, user_id, goods_id from test_import_export
            where
            seckill_id >=
            ( select seckill_id from test_import_export limit #{start},1)
            limit #{pageSize};
    

分页查询,单线程读,单线程导出

 @Test
    public void export1() throws FileNotFoundException {
        long start = System.currentTimeMillis();
        long max = 0;
        int pageSize = 50000;

        Long count = testImportExportMapper.selectCount(null);
        long totalPageNum  =  count % pageSize == 0 ? count/pageSize : count/pageSize + 1;

        String fileName = "D:\\repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        try (ExcelWriter excelWriter = EasyExcel.write(fileName, TestImportExport.class).build()) {
            for (int i = 1; i <= totalPageNum; i++) {
                log.info("正在读第{}页", i);
                List<TestImportExport> data = testImportExportMapper.findPage(max, pageSize);
                Optional<TestImportExport> max1 = data.stream().max(TestImportExportServiceImplTest2::compare);
                max = Math.max(max1.get().getSeckillId(), max);
                //导出
                WriteSheet writeSheet = EasyExcel.writerSheet(i, "页" + i).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                excelWriter.write(data, writeSheet);
            }
        }
        System.out.println("消耗:" + (System.currentTimeMillis() - start) / 1000);
    }

这里分页查,每页都导出到一个sheet中,有多少页导出到多少个sheet。使用第一种优化分页sql语句方案。

分页查询,多线程读,单线程导出

 @Test
    public void export2() throws FileNotFoundException, InterruptedException, ExecutionException {
        long start = System.currentTimeMillis();
        ExecutorService executorService = Executors.newFixedThreadPool(16 + 1);

        int pageSize = 50000;
        List<Callable<String>> callableList = new ArrayList<>();
        long count = testImportExportMapper.selectCount(null);
         
         //代码1
         long totalPageNum  =  count % pageSize == 0 ? count/pageSize : count/pageSize + 1;
        Map<Integer, List<TestImportExport>> dataMap = new ConcurrentHashMap<>();

        for (int i = 1; i <= totalPageNum; i++) {
            int finalI = i;
            Callable<String> callable = () -> {
                log.info("正在读第{}页", finalI);
                long start1 = (finalI - 1) * pageSize;
                List<TestImportExport> data = testImportExportMapper.findPage2(start1, pageSize);
                //代码2
                 dataMap.put(finalI, data);
                return "OK";
            };
            callableList.add(callable);
        }
        List<Future<String>> futures = executorService.invokeAll(callableList);
        for (Future<String> future : futures) {
            log.info(future.get());
        }
        System.out.println("准备数据消耗:" + (System.currentTimeMillis() - start) / 1000);

        String fileName = "D:\\repeatedWriteMiltyThread" + System.currentTimeMillis() + ".xlsx";
        
         //代码3
         try (ExcelWriter excelWriter = EasyExcel.write(fileName, TestImportExport.class).build()) {
            for (Integer pageNum : dataMap.keySet()) {
                //导出
                WriteSheet writeSheet = EasyExcel.writerSheet(pageNum, "页" + pageNum).build();
                // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
                excelWriter.write(dataMap.get(pageNum), writeSheet);

            }
        }
        System.out.println("消耗:" + (System.currentTimeMillis() - start) / 1000);
    }

代码1处先求出最大页数,然后一页定义一个线程去查询数据,最终汇总在dataMap中(如代码2处,key为页数,val为当页数据)。最终代码3处 将dataMap的key作为sheet的no,val为该sheet填充的数据,将dataMap写入到excel。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

涛然自得周刊(第06期):韩版苏东坡的突围

作者&#xff1a;何一涛 日期&#xff1a;2023 年 8 月 27 日 涛然自得周刊主要精选作者阅读过的书影音内容&#xff0c;不定期发布。历史周刊内容可以看这里。 电影 兹山鱼谱 讲述丁若铨因政治事件被贬黜到了遥远的黑山岛。来到岛上后&#xff0c;丁被大自然环境疗愈&#…

自定义温度显示控件(四) — 终结篇

概述 详细讲述自定义温度控件的实现 详细 前言 在之前的文章中&#xff0c;已经讲到了自定义温度显示控件一步步进化的历程&#xff0c;大家有兴趣的话可参考以下文章&#xff1a; 自定义温度显示控件(一) 自定义温度显示控件(二) 自定义温度显示控件(三) 今天讲温度实现效…

CPU深度解析

操作系统课程 计算机组成 ALU:计算单元(运算器)PC:pc寄存器存执行指令Registers:寄存器存数据MMU:控制器程序的构成:指令+数据 总线:一个程序读入内存,全是由0和1构成,从内存读取到cpu计算,需要通过总线。一段01数据段是指令还是数据是通过来源总线区分的。总线分…

农村农产品信息展示网站的设计与实现(论文+源码)_kaic

摘 要 随着软件技术的迅速发展,农产品信息展示的平台越来越多,传统的农产品显示方法将被计算机图形技术取代。这种网站技术主要把农产品的描述、农产品价格、农产品图片等内容&#xff0c;通过计算机网络的开发技术&#xff0c;在互联网上进行展示&#xff0c;然后通过计算机网…

9 串口通信(三)

9.4 USART串口数据包 HEX数据包 1&#xff09;固定包长&#xff0c;含包头包尾 例如陀螺仪的数据&#xff0c;需要XYZ坐标一起打包 2&#xff09;可变包长&#xff0c;含包头包尾 如果定义的包头包尾刚刚好也是数据&#xff0c;这样容易混淆&#xff0c;解决的办法&#x…

java-Optional 类详解

目录 前言 Optional的构造方法 Optional的相关方法介绍 isPresent用法&#xff1a; get用法&#xff1a; filter用法&#xff1a; orElse用法&#xff1a; orElseGet用法 orElseThrow用法 map用法 flatMap用法&#xff1a; 前言 Optional 类是java8的新特性&#xff0…

Redis—Redis介绍(是什么/为什么快/为什么做MySQL缓存等)

一、Redis是什么 Redis 是一种基于内存的数据库&#xff0c;对数据的读写操作都是在内存中完成&#xff0c;因此读写速度非常快&#xff0c;常用于缓存&#xff0c;消息队列、分布式锁等场景。 Redis 提供了多种数据类型来支持不同的业务场景&#xff0c;比如 String(字符串)、…

基于springboot学生社团管理系统/基于Java的高校社团管理系统的设计与实现

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&…

130.【Spring注解】

Spring 注解 (一)、AOP功能测试1.AOP 使用步骤(1).导入AOP对应的依赖(2).编写业务逻辑类(3).编写切面类(4).编写配置类(5). 编写测试类 (二)、AOP 原理1.EnableAspectJAutoProxy(1).EnableAspectJAutoProxy源码(2).AspectJAutoProxyRegistrar 自定义注册bean源码(3).打断点进行…

Harbor 私有仓库迁移博客

文章目录 Harbor 私有仓库迁移一.私有仓库迁移的介绍1.为何要对Harbor 私有仓库的迁移2.Harbor 私有仓库的迁移特点3. Harbor 私有仓库的迁移注意要点 二.私有仓库迁移配置1.源Harbor配置&#xff08;192.168.198.11&#xff09;&#xff08;1&#xff09;接着以下操作查看容器…

软考:中级软件设计师:信息系统的安全属性,对称加密和非对称加密,信息摘要,数字签名技术,数字信封与PGP

软考&#xff1a;中级软件设计师:信息系统的安全属性 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准…

零售再增长,直播登“C位”,美团稳稳交出成绩单

8月24日&#xff0c;美团发布2023年中期业绩和二季报&#xff0c;财报显示其二季度实现营收680亿元&#xff0c;同比增长33.4%&#xff1b;实现净利润47.13亿元&#xff0c;同比扭亏为盈&#xff0c;调整后净利润达历史最高水平。其中&#xff0c;与消费市场走势息息相关的美团…

腾讯云服务器建站教程_新手站长搭建网站全流程

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网txyfwq.com分享使用腾讯云服务器建站教程&#xff0c;…

字节律动之*你太美, emm 其实是个字符画雪花视频-哈哈哈-将视频转成一张张字符画图片

效果 整体效果 局部图片放大效果 视频转换后带雪花特效,凑合看吧, 视频地址 准备工作 安装FFmpeg 电脑上安装ffpeg处理视频并设置环境变量, windows可以参考FFmpeg的安装教程这篇博客安装 mac可以直接执行brew install ffmpeg安装 安装python依赖包 执行pip3 install -…

一篇掌握BFD技术(三):单臂回声配置

1. 实验目的 熟悉单臂回声的应用场景掌握单臂回声的配置方法 2. 实验拓扑 想要华为数通配套实验拓扑和配置笔记的朋友们点赞关注&#xff0c;评论区留下邮箱发给你 3. 实验步骤 1&#xff09;配置IP地址 AR1的配置 <Huawei>system-v…

【Unity笔记】TimeLine的详细使用介绍

文章目录 前言素材一、timeline基础介绍1. 打开timeline轨道面板2. 创建TimeLine轨道3. Timeline常用轨道4. 修改Timeline单位5. 锁定界面 二、timeline的通用轨道使用三、Cinemeachine虚拟相机结合Timeline实现场景移动四、DialogueTrack&#xff1a;自定义的对话轨道(自己编写…

JVM第三篇 运行时数据区-虚拟机栈和PC程序计数器

目录 1. JAVA中的线程 2. 栈区 2.1 栈帧 2.2 栈可能出现的异常 2.3 设置栈大小 3.程序计数器&#xff08;PC&#xff09; 4. PC和栈发挥的作用 5. 关于栈的常见面试题 虚拟机包含三大部分&#xff0c;类加载子系统&#xff0c;运行时数据区&#xff0c;执行引擎。运行时…

基于亚马逊云科技无服务器服务快速搭建电商平台——性能篇

使用 Serverless 构建独立站的优势 在传统架构模式下&#xff0c;如果需要进行电商大促需要提前预置计算资源以支撑高并发访问&#xff0c;会造成计算资源浪费并且增加运维工作量。本文介绍一种新的部署方式&#xff0c;将 WordPress 和 WooCommerce 部署在 Amazon Lambda 中。…

C++信息学奥赛1149:最长单词2

#include <iostream> #include <string> using namespace std; int main() {string str1;// 输入一行字符串getline(cin,str1);int n0;string MaxArr"";string MinArrstr1;string arr"";for(int i0;i<str1.length();i){if(str1[i] or str1…