You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot读取50万行Excel后执行saveAll时触发SocketTimeoutException

问题:导入50万行Excel后执行saveAll()出现SocketTimeoutException且无控制台异常输出

在Spring Boot应用中读取包含50万余行数据的Excel文件时,已完成所有行的读取操作后,出现java.net.SocketTimeoutException: Read timed out异常,且控制台未显示任何错误或异常信息。经排查发现,代码执行到xyzsRepository.saveAll()方法时停滞,相关代码如下:

private List<XYZEntry> importFile(XYZ file) throws ServiceException {
    var xyzToImport = new HashSet<XYZ>();
    List<XYZEntry> invalidXYZ = new ArrayList<>();
    try(var wb = WorkbookFactory.create(new ByteArrayInputStream(file.getContent()))) {
        for(var row: wb.getSheetAt(0)) {
            if(row.getRowNum() == 0) {
                // 跳过表头
                continue;
            }
            DataFormatter formatter = new DataFormatter();
            var potentialXYZ = formatter.formatCellValue(row.getCell(0)).trim();
            if(isXYZValidToImport(potentialXYZ)) {
                xyzsToImport.add(new XYZ(potentialXYZ)); // 注意:变量名与定义的xyzToImport不一致,存在笔误
            } else {
                invalidXYZ.add(new XYZEntry(potentialXYZ, row.getRowNum() + 1));
            }
        }
        if(CollectionUtils.isNotEmpty(invalidXYZ)) {
            file.setInvalidEntries(invalidXYZ.size());
            log.info(
                "导入文件{}时,跳过了{}条无效XYZ:{}",
                file.getFilename(),
                invalidXYZ.size(),
                String.join(",", invalidXYZ.toString())
            );
        }
        if(CollectionUtils.isEmpty(xyzToImport)) {
            log.warn(
                "文件{}中未找到有效XYZ,无数据导入。", 
                file.getFilename()
            );
            file.setStatus(XYZStateEnum.NO_IMPORTS);
        } else {
            transactionHelper.executeInNewTransaction(() -> {
                xyzsRepository.deleteAll();
                xyzsRepository.saveAll(xyzsToImport);
                // 此处代码无法到达
                return null;
            });
            file.setValidEntries(xyzsToImport.size());
            file.setStatus(XYZStateEnum.IMPORTED);
        }
    } catch(IOException e) {
        file.setStatus(XYZStateEnum.ERROR);
        filesRepository.save(file);
        throw new ServiceException("XYZ导入失败。", e);
    }
    filesRepository.save(file);
    return invalidXYZ;
}

问题原因分析

  1. 变量名笔误:代码中定义的变量是xyzToImport,但后续添加元素时使用的是xyzsToImport,会导致编译错误,需优先修正。
  2. 批量插入效率低下:直接调用saveAll()插入50万条数据,默认会逐条执行SQL(Hibernate默认行为),耗时极长,触发数据库连接超时。
  3. 事务未设置超时:新事务中执行deleteAll()+saveAll()大操作,未配置事务超时时间,导致底层数据库连接因等待过久超时。
  4. 异常未完整捕获:事务内部的异常未被显式捕获输出,仅在底层抛出SocketTimeoutException,控制台无异常日志。
  5. 数据库连接超时配置不足:数据库连接池或数据库本身的超时阈值设置过短,无法支撑大批次数据操作的耗时。

修复方案

1. 修正变量名笔误

将xyzToImport统一修正为xyzsToImport,确保变量引用一致:

var xyzsToImport = new HashSet<XYZ>();

2. 优化批量插入逻辑

开启JPA批量插入支持,并分批次插入数据,降低内存占用和数据库压力:

  • 配置文件(application.yml)添加批量插入参数:
spring:
  jpa:
    properties:
      hibernate:
        jdbc:
          batch_size: 500  # 每批次插入500条
        order_inserts: true  # 确保插入语句按顺序执行,提升批量效率
        order_updates: true
  • 改造saveAll()为分批次插入:
transactionHelper.executeInNewTransaction(() -> {
    xyzsRepository.deleteAllInBatch(); // 用批量删除替代deleteAll(),提升效率
    List<XYZ> xyzList = new ArrayList<>(xyzsToImport);
    int batchSize = 500;
    for (int i = 0; i < xyzList.size(); i += batchSize) {
        int endIdx = Math.min(i + batchSize, xyzList.size());
        xyzsRepository.saveAll(xyzList.subList(i, endIdx));
        // 手动刷新并清理实体管理器,避免内存溢出
        entityManager.flush();
        entityManager.clear();
    }
    return null;
});

注:需注入EntityManager实例到当前类中使用。

3. 设置事务超时

为大操作事务设置足够的超时时间(例如300秒),避免事务未完成就被中断:
如果transactionHelper支持传入超时参数,可直接指定:

transactionHelper.executeInNewTransaction(() -> {
    // 批量操作逻辑
    return null;
}, 300); // 超时时间300秒

如果是使用@Transactional注解,可添加timeout属性:

@Transactional(timeout = 300)
public void batchImport(List<XYZ> xyzList) {
    // 批量操作逻辑
}

4. 完善异常捕获与日志输出

在事务内部添加异常捕获,确保异常能被记录到控制台:

transactionHelper.executeInNewTransaction(() -> {
    try {
        xyzsRepository.deleteAllInBatch();
        // 分批次插入逻辑
        return null;
    } catch (Exception e) {
        log.error("批量导入XYZ数据失败", e);
        throw e; // 抛出异常触发事务回滚
    }
});

5. 调整数据库连接超时配置

修改数据库连接池(以HikariCP为例)和数据库的超时参数,适配大批次操作:

  • 连接池配置:
spring:
  datasource:
    hikari:
      connection-timeout: 300000  # 连接超时5分钟
      idle-timeout: 300000        # 空闲连接超时5分钟
      max-lifetime: 600000        # 连接最大生命周期10分钟
  • MySQL数据库配置(my.cnf):
wait_timeout = 600        # 非交互连接超时10分钟
interactive_timeout = 600 # 交互连接超时10分钟

内容的提问来源于stack exchange,提问作者tdog

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 15:55:08