Excel高性能导入方案!

大模型数据库云通信

picture.image

苏三的免费八股文网站:

www.susan.net.cn

大家好,我是苏三,又跟大家见面了。

前言

今天要讨论一个让无数人抓狂的话题: 如何高效导入百万级Excel数据

去年有家公司找到我,他们的电商系统遇到一个致命问题:每天需要导入20万条商品数据,但一执行就卡死,最长耗时超过3小时。

更魔幻的是,重启服务器后前功尽弃。

经过半天的源码分析,我们发现了下面这些触目惊心的代码...

1 为什么传统导入方案会崩盘?

很多小伙伴在实现Excel导入时,往往直接写出这样的代码:


        
        
            

          
 // 错误示例:逐行读取+逐条插入
 
          
   

 
          
 
 public
 
  
 
 void
 
  
 
 importExcel
 
 
 (File file)
 
  
 
          {
          
   

 
              List<Product> list = ExcelUtils.readAll(file); 
          
 // 一次加载到内存
 
          
   

 
              
          
 for
 
           (Product product : list) {
          
   

 
                  productMapper.insert(product); 
          
 // 逐行插入
 
          
   

 
              }
          
   

 
          }
          
   

 
        
      

这种写法会引发三大致命问题:

1.1 内存熔断:堆区OOM惨案

  • 问题 :POI的 UserModel (如XSSFWorkbook)一次性加载整个Excel到内存
  • 实验 :一个50MB的Excel(约20万行)直接耗尽默认的1GB堆内存
  • 症状 :频繁Full GC ➔ CPU飙升 ➔ 服务无响应

1.2 同步阻塞:用户等到崩溃

  • 过程 :用户上传文件 → 同步等待所有数据处理完毕 → 返回结果
  • 风险 :连接超时(HTTP默认30秒断开)→ 任务丢失

1.3 效率黑洞:逐条操作事务

  • 实测数据 :MySQL单线程逐条插入≈200条/秒 → 处理20万行≈16分钟
  • 幕后黑手 :每次insert都涉及事务提交、索引维护、日志写入

2 性能优化四板斧

第一招:流式解析

使用POI的SAX模式替代DOM模式:


        
        
            

          
 // 正确写法:分段读取(以HSSF为例)
 
          
   

 
          OPCPackage pkg = OPCPackage.open(file);
          
   

 
          XSSFReader reader = 
          
 new
 
           XSSFReader(pkg);
          
   

 
          SheetIterator sheets = (SheetIterator) reader.getSheetsData();
          
   

 
          
   

 
          
 while
 
           (sheets.hasNext()) {
          
   

 
              
          
 try
 
           (InputStream stream = sheets.next()) {
          
   

 
                  Sheet sheet = 
          
 new
 
           XSSFSheet(); 
          
 // 流式解析
 
          
   

 
                  RowHandler rowHandler = 
          
 new
 
           RowHandler();
          
   

 
                  sheet.onRow(row -> rowHandler.process(row));
          
   

 
                  sheet.process(stream); 
          
 // 不加载全量数据
 
          
   

 
              }
          
   

 
          }
          
   

 
        
      

⚠️ 避坑指南

  • 不同Excel版本需适配(HSSF/XSSF/SXSSF)
  • 避免在解析过程中创建大量对象,需复用数据容器

第二招:分页批量插入

基于MyBatis的批量插入+连接池优化:


        
        
            

          
 // 分页批量插入(每1000条提交一次)
 
          
   

 
          
 
 public
 
  
 
 void
 
  
 
 batchInsert
 
 
 (List<Product> list)
 
  
 
          {
          
   

 
              SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
          
   

 
              ProductMapper mapper = sqlSession.getMapper(ProductMapper
          
 .
 
 class
 
 )
 
          ;
          
   

 
              
          
   

 
              
          
 int
 
           pageSize = 
          
 1000
 
          ;
          
   

 
              
          
 for
 
           (
          
 int
 
           i = 
          
 0
 
          ; i < list.size(); i += pageSize) {
          
   

 
                  List<Product> subList = list.subList(i, Math.min(i + pageSize, list.size()));
          
   

 
                  mapper.batchInsert(subList);
          
   

 
                  sqlSession.commit();
          
   

 
                  sqlSession.clearCache(); 
          
 // 清理缓存
 
          
   

 
              }
          
   

 
          }
          
   

 
        
      

关键参数调优


        
        
            

          
 # MyBatis配置
 
          
   

 
          
 mybatis.executor.batch.size=1000
 
          
   

 
          
   

 
          
 # 连接池(Druid)
 
          
   

 
          
 spring.datasource.druid.maxActive=50
 
          
   

 
          
 spring.datasource.druid.initialSize=10
 
          
   

 
        
      

第三招:异步化处理

架构设计:picture.image

  1. 前端上传 :客户端使用WebUploader等分片上传工具
  2. 服务端
  • 生成唯一任务ID
  • 写入任务队列(Redis Stream/RabbitMQ)
  • 异步线程池
  • 多线程消费队列
  • 处理进度存储在Redis中
  • 结果通知 :通过WebSocket或邮件推送完成状态

第四招:并行导入

对于千万级数据,可采用分治策略:

阶段操作耗时对比
单线程
逐条读取+逐条插入
基准值100%
批处理
分页读取+批量插入
时间降至5%
多线程分片
按Sheet分片,并行处理
时间降至1%
分布式分片
多节点协同处理(如Spring Batch集群)
时间降至0.5%

3 代码之外的关键经验

3.1 数据校验必须前置

典型代码缺陷:


        
        
            

          
 // 错误:边插入边校验,可能污染数据库
 
          
   

 
          
 
 public
 
  
 
 void
 
  
 
 validateAndInsert
 
 
 (Product product)
 
  
 
          {
          
   

 
              
          
 if
 
           (product.getPrice() < 
          
 0
 
          ) {
          
   

 
                  
          
 throw
 
           
          
 new
 
           Exception(
          
 "价格不能为负"
 
          );
          
   

 
              }
          
   

 
              productMapper.insert(product);
          
   

 
          }
          
   

 
        
      

正确实践

  1. 在流式解析阶段完成基础校验(格式、必填项)
  2. 入库前做业务校验(数据关联性、唯一性)

3.2 断点续传设计

解决方案:

  • 记录每个分片的处理状态
  • 失败时根据偏移量(offset)恢复

3.3 日志与监控

配置要点:


        
        
            

          
 // Spring Boot配置Prometheus指标
 
          
   

 
          
 @Bean
 
          
   

 
          
 
 public
 
  MeterRegistryCustomizer<PrometheusMeterRegistry> 
 
 metrics
 
 
 ()
 
  
 
          {
          
   

 
              
          
 return
 
           registry -> registry.config().meterFilter(
          
   

 
                  
          
 new
 
           MeterFilter() {
          
   

 
                      
          
 @Override
 
          
   

 
                      
          
 
 public
 
  DistributionStatisticConfig 
 
 configure
 
 
 (Meter.Id id, DistributionStatisticConfig config)
 
  
 
          {
          
   

 
                          
          
 return
 
           DistributionStatisticConfig.builder()
          
   

 
                              .percentiles(
          
 0.5
 
          , 
          
 0.95
 
          ) 
          
 // 统计中位数和95分位
 
          
   

 
                              .build().merge(config);
          
   

 
                      }
          
   

 
                  }
          
   

 
              );
          
   

 
          }
          
   

 
        
      

四、百万级导入性能实测对比

测试环境:

  • 服务器:4核8G,MySQL 8.0
  • 数据量:100万行x15列(约200MB Excel)
方案内存峰值耗时吞吐量
传统逐条插入
2.5GB
96分钟
173条/秒
分页读取+批量插入
500MB
7分钟
2381条/秒
多线程分片+异步批量
800MB
86秒
11627条/秒
分布式分片(3节点)
300MB/节点
29秒
34482条/秒

总结

Excel高性能导入的11条军规:

  1. 决不允许全量加载数据到内存 → 使用SAX流式解析
  2. 避免逐行操作数据库 → 批量插入加持
  3. 永远不要让用户等待 → 异步处理+进度查询
  4. 横向扩展比纵向优化更有效 → 分片+分布式计算
  5. 内存管理是生死线 → 对象池+避免临时大对象
  6. 合理配置连接池参数 → 杜绝瓶颈在数据源
  7. 前置校验绝不动摇 → 脏数据必须拦截在入口
  8. 监控务必完善 → 掌握全链路指标
  9. 设计必须支持容灾 → 断点续传+幂等处理
  10. 抛弃单机思维 → 拥抱分布式系统设计
  11. 测试要覆盖极端场景 → 百万数据压测不可少

如果你正在为Excel导入性能苦恼,希望这篇文章能为你的系统打开一扇新的大门。

如果你有其他想了解的技术难题,欢迎在评论区留言!

最后欢迎

加入苏三的星球

,你将获得:AI开发项目课程、苏三AI项目、

商城微服务实战、秒杀系统实战

商城系统实战、秒杀系统实战、代码生成工具、系统设计、性能优化、技术选型、底层原理、Spring源码解读、工作经验分享、痛点问题

、面试八股文

等多个优质专栏。

还有1V1答疑、修改简历、职业规划、送书活动、技术交流。

扫描下方二维码,即可加入星球:

picture.image

目前星球已经更新了 5200+ 篇优质内容,还在持续爆肝中.....

星球已经被 官方推荐了3次 ,收到了小伙伴们的一致好评。戳我加入学习,已有 1600+ 小伙伴加入学习。

picture.image

苏三的免费八股文网站:

www.susan.net.cn

picture.image

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
基于火山引擎 EMR 构建企业级数据湖仓
火山引擎 EMR 是一款云原生开源大数据平台,提供主流的开源大数据引擎,加持了字节跳动内部的优化、海量数据处理的最佳实践。本次演讲将为大家介绍火山引擎 EMR 的架构及核心特性,如何基于开源架构构建企业级数据湖仓,同时向大家介绍火山 EMR 产品的未来规划。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论