前言

在实际工作中,我们经常会遇到 月底运营或客户要求导出大量业务数据 的情况。如果数据量在 几万行 以内,普通导出方式尚能应对。但当数据量增长到 几十万甚至上百万行 时,传统方法往往会 性能下降、内存溢出,甚至导致服务器崩溃。那么,该如何高效地处理 百万级 Excel 数据导出 呢?本文将分享优化方案,帮助你轻松应对大数据导出挑战。

思路

分治思想:分批查询 + 分页写入
一次性查询 100 万条数据,数据库和服务器的内存往往会承受不了,导致 查询超时、系统崩溃。因此,我们可以采用 分批查询 + 分页写入 的方式,避免内存占用过高。

思路如下:

分页查询数据:将 100 万条数据 分成多个批次,每次查询 1 万条,减少数据库压力。

循环写入 Excel:查询到 1 万条数据后,立即写入 Excel,避免一次性加载所有数据到内存。

封装导出方法:将 分页查询 + 写入逻辑 封装成通用方法,自动处理大数据导出。

代码实现

这里使用EasyExcel执行导出操作,DEFAULT_PAGE_SIZE 可根据实际需要修改对应值。

package com.ji.jichat.excel.util.service;

import cn.hutool.core.date.StopWatch;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.github.pagehelper.ISelect;
import com.ji.jichat.common.pojo.PageDTO;
import com.ji.jichat.common.pojo.PageVO;
import com.ji.jichat.mybatis.util.JiPageHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class ExcelExportService<T> {

    /**
     * 每次查询的默认分页大小
     **/
    private static final int DEFAULT_PAGE_SIZE = 5000;

    /**
     * 导出excel
     *
     * @param fileName       导出文件名
     * @param query          导出的查询方法
     * @param dataModelClass 导出返回实体类
     * @author jisl on 2025/2/6 14:58
     **/
    public void exportToExcel(String fileName, ISelect query, Class<T> dataModelClass) {
        // 获取总记录数
        final StopWatch stopWatch = new StopWatch("导出开始:" + fileName);
        stopWatch.start("总数查询");
        final long totalRecords = JiPageHelper.doSelectPageInfo(new PageDTO(1, 10), query).getTotal();
        final long totalPages = (totalRecords + DEFAULT_PAGE_SIZE - 1) / DEFAULT_PAGE_SIZE;
        log.info("导出totalRecords=%s,totalPages=%s", totalRecords, totalPages);
        stopWatch.stop();
        stopWatch.start("分页导出");
        // 创建 ExcelWriter 对象
        final ExcelWriter excelWriter = EasyExcel.write(fileName).build();
        // 分页查询并写入数据
        for (int currentPage = 1; currentPage <= totalPages; currentPage++) {
            // 分页查询数据
            final PageVO<T> pageVO = JiPageHelper.doSelectPageInfo(new PageDTO(currentPage, DEFAULT_PAGE_SIZE), query);
            // 写入数据到当前页的Sheet
            WriteSheet writeSheet = EasyExcel.writerSheet("Sheet " + currentPage).head(dataModelClass).build();
            excelWriter.write(pageVO.getList(), writeSheet);
        }
        // 完成写入
        excelWriter.finish();
        stopWatch.stop();
        log.info("导出用时:" + stopWatch.prettyPrint(TimeUnit.MILLISECONDS));
    }
}

导出调用方法

    @Test
    public void exportDataToExcel() {
        ExcelExportService excelExportService=new ExcelExportService();
        final SystemSmsCodePageDTO dto = SystemSmsCodePageDTO.builder().build();
        excelExportService.exportToExcel("C:\\Newand\\file\\导出测试.xlsx", () -> systemSmsCodeService.page(dto), SystemSmsCodeExcelVO.class);
    }

实际效果

导出用时:StopWatch '导出开始:C:\Newand\file\导出测试.xlsx': running time = 4528 ms
---------------------------------------------
ms         %     Task name
---------------------------------------------
000000477  11%   总数查询
000004051  89%   分页导出

源码

Excel导出github源码

下载

在处理百万级数据量的 Excel 导出时,直接通过 HTTP 让用户等待文件生成是不可取的,因为:

响应超时:HTTP 请求有超时时间,长时间等待会导致请求失败。

用户体验差:用户需要长时间等待,无法进行其他操作。

优化方案:异步导出 + 个人中心下载
更好的解决方案是:


用户提交导出请求 → 后台异步处理,立即返回任务提交成功的响应。

后台任务生成 Excel → 任务执行完成后,将文件存储至服务器或云存储。

用户个人中心查看进度 → 处理完成后,用户可以在个人中心查看并下载文件。

总结

以上是笔者在大数据导出方面的一些实践经验分享,希望对大家有所帮助。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐