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
相关产品推荐
相关产品推荐

