十万级百万级数据量的Excel文件导入并写入数据库

一.需求分析

  最近接到一个需求,导入十万级,甚至可能百万数据量的记录了车辆黑名单的Excel文件,借此机会分析下编码过程;

  首先将这个需求拆解,发现有三个比较复杂的问题:

  问题一:Excel文件导入后首先要被解析为存放对象的列表,数据量大的情况下可能会导致内存溢出,解析时间过长;

  问题二:插入数据库的时候,数据量大,写入的时间长

  问题三:要对数据库中的现有数据进项判断,不仅仅要做插入动作,还要将数据库的数据与导入的数据对比,判断是否做更新操作

  

  其中:

  问题一和问题三,可以看做同一类,因为主要涉及内存计算导致的性能问题,以及内存占用过大的溢出问题,

  关于这两个问题,现在线上的机器基本上是4核8G的配置集群部署,内存并不是关键,我会在另一篇文章中给出我的方案,

  今天主要针对问题二,写入的数据库的问题给出我的方案,

  

  问题二主要是多次写入数据库的问题,显然,如果有几十万条数据,那么是不可能连续写几十万次的,不然要写到后年马月才能全部入库,

  解决方案:

    这里我主要采用了多线程的写入方式,十万条数据,2000条写一次(可以自己定义),用线程池提交多个线程任务同时写入,提高性能

二.代码环境

  Springboot2.1.3+POI+PGSQL

  controller层代码

    @PostMapping("/upload")
    public void upload1(MultipartFile file, @Validated UploadReq req) throws Exception {
        //从数据库查询出现有的数据,根据去重的字段分组去构建成一个HashMap,通过containsKey()判断
        //将需要更新的数据放到updateList中
        List<User> updateList=new ArrayList<>();

        //已取值的行数
        int rowNum = 0;
        //列号
        int colNum = 0;
        //真正有数据的行数
        int realRowCount = 0;
        //得到工作空间
        Workbook workbook = null;

        try {
            workbook = ExcelUtil.getWorkbookByInputStream(file.getInputStream(), file.getOriginalFilename());
        } catch (IOException e) {
            e.printStackTrace();
        }
        //得到工作表
        int numberOfSheets = workbook.getNumberOfSheets();
        for (int i = 0; i < numberOfSheets; i++) {
            Sheet sheet = ExcelUtil.getSheetByWorkbook(workbook, i)
            realRowCount = sheet.getPhysicalNumberOfRows();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            List<User> list = new ArrayList<>();
            User user = null;

            for(Row row:sheet) {
                if(realRowCount == rowNum) {
                    break;
                }
                //空行跳过
                if(ExcelUtil.isBlankRow(row)) {
                    continue;
                }
                if(row.getRowNum() == -1) {
                    continue;
                }else {
                    //第一行表头跳过
                    if(row.getRowNum() == 0) {
                        continue;
                    }
                }
                rowNum ++;
                colNum = 1;
                user = new User();
                ExcelUtil.validCellValue(sheet, row, colNum, "id");
                user.setId(Integer.valueOf(ExcelUtil.getCellValue(sheet, row, colNum - 1)));
                ExcelUtil.validCellValue(sheet, row, ++ colNum, "name");
                user.setId(Integer.valueOf(ExcelUtil.getCellValue(sheet, row, colNum - 1)));
                //判断是否是已存在的数据,如果是就更新,不是就新增
                //updateList.add(user);
                list.add(user);

            }

            //新增的逻辑
            userService.saveBatch(list);
            System.out.println(list);
        }
    }

  service层代码

@Service
public class UserServiceImpl implements IUserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public void saveBatch(List<User> list) throws Exception {
        //一个线程处理200条数据
        int count = 200;
        //数据集合大小
        int listSize = list.size();
        //开启的线程数
        int runSize = (listSize / count) + 1;
        //存放每个线程的执行数据
        List<User> newlist = null;

        //创建一个线程池,数量和开启线程的数量一样
        //Executors 的写法
        // ExecutorService executor = Executors.newFixedThreadPool(runSize);

        //ThreadPoolExecutor的写法
        ThreadPoolExecutor executor = new ThreadPoolExecutor(runSize, runSize, 1,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.DiscardOldestPolicy());

        //创建两个个计数器
        CountDownLatch begin = new CountDownLatch(1);
        CountDownLatch end = new CountDownLatch(runSize);
        //循环创建线程
        for (int i = 0; i < runSize; i++) {
            //计算每个线程执行的数据
            if ((i + 1) == runSize) {
                int startIndex = (i * count);
                int endIndex = list.size();
                newlist = list.subList(startIndex, endIndex);
            } else {
                int startIndex = (i * count);
                int endIndex = (i + 1) * count;
                newlist = list.subList(startIndex, endIndex);
            }
            //线程类
            ImportThread mythead = new ImportThread(newlist, begin, end,userMapper);
            //这里执行线程的方式是调用线程池里的executor.execute(mythead)方法。
            executor.execute(mythead);
        }
        begin.countDown();
        end.await();
        //执行完关闭线程池
        executor.shutdown();
    }

  线程类

public class ImportThread implements Runnable {

    public ImportThread() {
    }

    UserMapper userMapper;
    private List<User> list;
    private CountDownLatch begin;
    private CountDownLatch end;

    /**
     * 方法名: ImportThread
     * 方法描述: 创建个构造函数初始化 list,和其他用到的参数
     * @throws
     */
    public ImportThread(List<User> list, CountDownLatch begin, CountDownLatch end,UserMapper userMapper) {
        this.list = list;
        this.begin = begin;
        this.end = end;
        this.userMapper=userMapper;
    }

    @Override
    public void run() {
        try {
            //执行完让线程直接进入等待
            userMapper.saveBatch(list);
            begin.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //这里要主要了,当一个线程执行完 了计数要减一不然这个线程会被一直挂起
            //这个方法就是直接把计数器减一的
            end.countDown();
        }
    }

}

  

  

  

原文地址:https://www.cnblogs.com/gabriel-y/p/11761482.html

时间: 2024-08-01 14:12:31

十万级百万级数据量的Excel文件导入并写入数据库的相关文章

批量将制定文件夹下的全部Excel文件导入微软SQL数据库

以下代码将c:\cs\文件夹下的全部Excle中数据导入到SQL数据库 declare @query vARCHAR(1000) declare @max1 int declare @count1 int declare @filename varchar(100) set @count1=0 create table #x(name varchar(200)) insert #x exec('master.dbo.xp_cmdshell  ''dir c:\cs\*.xls  /b''') d

Excel文件导入SQL Server数据库表

--office 2003--如果接受数据导入的表已经存在insert into DemoTable select * from OPENROWSET('MICROSOFT.JET.OLEDB.4.0','Excel 5.0;HDR=YES;DATABASE=d:/Demo.xls',sheet1$)--如果导入数据并生成表select * into DemoTable  from OPENROWSET('MICROSOFT.JET.OLEDB.4.0','Excel 5.0;HDR=YES;D

.NET从EXCEL文件导入数据

.NET com组件 这种方法在计算机没有安装office套件时,也是能够使用的.所以不依赖于软件, 但是还是需要xcel.exe编译后的dll文件打包到相应的程序中来引用.这样将dll文件" 随身携带",就可以了.还是挺不错的! 1.注册Microsoft.Office.Interop.Excel.dll 在office安装文件夹下找到excel.exe,路径D:\Program Files(x86)\Microsoft Office\Office15.将excel.exe文件复制到

关于Excel文件导入到Sqlserver2008中出现截断错误的解决办法

出现错误的可能原因: 1.数据库字段Varchar长度不够: 2.不能用Text类型: 3.数据中可能存在换行符: 4.数据项文本过长,超过4000: 5.前8行的最大长度不够大,后面有超过的. 解决办法: 1.修改Varchar长度为足够长: 2.替换掉换行符(可手工输入一个换行符,复制到替换文本框中,Office中可按住Alt键,从小键盘输入10,再松开Alt键): 3.第一行中记录设足够长,导入后再替换: 4.把Excel文件存为2007格式,可解决导入后很多数据项为NULL的问题: 5.

将excel文件导入到数据库

参考:http://blog.csdn.net/jayxujia123/article/details/13684313 参考:http://kevin850115.iteye.com/blog/578142 MySQL官网:http://dev.mysql.com/doc/refman/5.7/en/load-data.html 学弟有个面试题,在群里问了下如何将excel文件导入到数据库.以前也没试过,正好在学习MySQL,练练手吧. 首先,将excel文件另存为用逗号分隔的csv文件,传到

excel文件导入数据库

1.准备jar包:poi 2.前端jsp页面: <body> <!-- form表单中必须添加enctype="multipart/form-data"才可以上传文件 servlet中根据file名称来获取文件相对路径 --><form action="/phoneQuery" method="post" enctype="multipart/form-data">    <table

excel文件导入mysql

在数据处理的过程中,常常要把windows下的excel文件导入linux下的mysql.这其中会出现一些问题. 1.首先,要在mysql中建表.命令最好存在记事本中,可以随时修改,随时执行 create table tableName(c1 not null,...); 2.把excel中需要导入的内容截出来.然后利用excel的另存为功能存成txt,不管编码方式是utf-8还是unicode都可以. 3.但是无论是utf-8还是unicode,linux/mysql都是认不出来的,需要转换成

asp.net 从Excel表导入数据到数据库中

http://www.cnblogs.com/hfzsjz/archive/2010/12/31/1922901.html http://hi.baidu.com/ctguyg/item/ebc857e90e436ae1fb42ba01 1 <form action="" method="post" runat ="server"> 2 <div> 3 <span>请选择文件:</span><

PHP 实时生成并下载超大数据量的 Excel 文件

//另外由于excel数据是从数据库里逐步读出然后写入输出流的所以需要将PHP的执行时间设长一点 //(默认30秒)set_time_limit(0)不对PHP执行时间做限制. set_time_limit(0); $columns = [ '文章ID', '文章标题', ...... //'openid' ]; //处理需要导出的数据 $timeStart = strtotime('2018-08-08 00:00:00'); $timeEnd = strtotime('2018-08-08