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

Java写入10万条CSV数据耗时过长问题排查与优化

Java写入CSV过慢问题修复方案

你的代码存在逻辑bug、资源泄漏、JDBC配置不合理、IO效率低四类问题,直接导致写入性能差,具体修复点如下:

核心问题点

  • JDBC结果集默认全量拉取到JVM内存:MySQL驱动(其他数据库逻辑类似)默认会把executeQuery返回的所有结果加载到堆内存,10万条数据如果字段较长会占用大量堆空间,触发频繁Full GC,STW停顿会直接拖慢整体写入速度,这是最可能导致写入耗时超1小时的核心原因。
  • 文件拆分逻辑存在缺陷:用while判断切文件条件属于冗余风险写法,且计数触发时机错误,写完第1001条才会触发切文件,不仅文件条数不符合预期,极端场景下可能出现异常循环。
  • 资源释放逻辑完全错误:finally块中的if语句没有加括号,仅第一行代码受if条件判断约束,不管rs/con/writer是否初始化成功,都会执行后续的close操作,很容易触发空指针异常,而且单个资源关闭失败会导致后续所有资源泄漏,文件句柄被占满后写入速度会骤降。
  • IO缓冲区过小:BufferedWriter默认缓冲区仅8KB,顺序写场景下会触发大量系统调用,额外增加开销。
  • 业务逻辑bug:rs.getString(3)没有做null值处理,字段为null时会被直接拼接成"null"字面量;没有做CSV转义,字段内含逗号、换行、双引号时会生成格式错误的CSV文件。
  • 环境IO阻塞:如果写入目录在iCloud/OneDrive等同步盘、或者开启了实时杀毒扫描,每次关闭文件触发强制刷盘时,都会同步触发文件上传/病毒扫描,占满磁盘IO带宽。

优化后代码

// 提前定义常量,方便调整
private static final int BATCH_SIZE = 1000;
private static final int BUFFER_SIZE = 65536; // 64KB缓冲区
private static final String FILE_PATH_PREFIX = "/Users/xxx/Documents/Deployments/";
private static final String FILE_SUFFIX = "CSVTaskFileUnion.csv";

public void writeCsv() {
    Connection con = null;
    Statement stmt = null;
    ResultSet rs = null;
    BufferedWriter writer = null;
    // 计数器提前初始化
    AtomicInteger success = new AtomicInteger(0);
    AtomicInteger filecount = new AtomicInteger(0);

    try {
        con = DriverManager.getConnection("url", "Username", "pwd");
        // 配置JDBC流式读取,避免全量数据加载到内存(MySQL配置,其他数据库可调整fetchSize为1000即可)
        stmt = con.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
        stmt.setFetchSize(Integer.MIN_VALUE);
        rs = stmt.executeQuery(SQL);

        // 初始化第一个文件
        File firstFile = new File(FILE_PATH_PREFIX + filecount.get() + FILE_SUFFIX);
        writer = new BufferedWriter(new FileWriter(firstFile), BUFFER_SIZE);
        filecount.incrementAndGet();

        while(rs.next()) {
            // 统一处理null值,必要时加CSV转义逻辑
            String col1 = Objects.toString(rs.getString(1), "");
            String col2 = Objects.toString(rs.getString(2), "");
            String col3 = Objects.toString(rs.getString(3), "");
            String col4 = Objects.toString(rs.getString(4), "");
            // 用StringBuilder拼接减少临时对象
            String line = new StringBuilder(col1)
                    .append(",").append(col2)
                    .append(",").append(col3)
                    .append(",").append(col4)
                    .toString();

            writer.write(line);
            writer.newLine();
            int currentCount = success.incrementAndGet();

            // 满BATCH_SIZE条就切文件,用if判断即可,不需要while
            if (currentCount >= BATCH_SIZE) {
                writer.flush();
                writer.close();
                // 创建新文件
                File newFile = new File(FILE_PATH_PREFIX + filecount.get() + FILE_SUFFIX);
                writer = new BufferedWriter(new FileWriter(newFile), BUFFER_SIZE);
                filecount.incrementAndGet();
                success.set(0);
            }
        }
    } catch (Exception e) {
        // 打印完整异常栈,方便排查问题
        logger.error("write csv error", e);
    } finally {
        // 每个资源单独关闭,单独捕获异常,避免单个资源关闭失败影响其他资源
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException e) {
                logger.error("close rs error", e);
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                logger.error("close stmt error", e);
            }
        }
        if (con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                logger.error("close con error", e);
            }
        }
        if (writer != null) {
            try {
                writer.flush();
                writer.close();
            } catch (IOException e) {
                logger.error("close writer error", e);
            }
        }
    }
}

额外优化建议

  • 不要把输出目录放在系统同步盘、下载目录等会被实时扫描的路径下,写入时临时关闭对应目录的杀毒实时扫描,排除IO干扰。
  • 如果需要更高的写入性能,可以直接用成熟的CSV序列化库(比如commons-csv),自带转义逻辑和缓冲区优化,比自己拼接字符串稳定性更高。
  • JVM参数可以适当调大堆内存,避免遍历结果集时触发频繁GC。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 03:06:29