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

Java读取3000万行PostgreSQL表:fetchSize设置后仅获首1万行的解决方法

解决PostgreSQL JDBC按批次读取3000万行并批量插入的问题

看起来你遇到的问题主要有两个核心点:一是错误的批量插入方式导致你误以为只读取了前1万行,二是PostgreSQL JDBC驱动对fetchSize的特殊处理需要额外注意。咱们一步步来修正你的代码:

问题分析

  1. 每次循环单条执行+立即提交:你在while(rs.next())里每次都调用pst.executeUpdate()和connection.commit(),这不仅效率极低,而且频繁的提交操作可能干扰ResultSet的批次读取逻辑,导致提前终止。
  2. PostgreSQL JDBC对fetchSize的要求:即使你设置了st.setFetchSize(10000),PostgreSQL驱动默认会忽略TYPE_FORWARD_ONLY ResultSet的fetchSize设置,除非你同时满足两个条件:connection.setAutoCommit(false)(你已经做了),以及确保驱动没有强制一次性加载所有数据。

修正后的代码

import java.sql.*;
import java.util.Date;

public class InsertBatch {
    public static void main(String[] args) throws SQLException {
        // 把数据库配置抽成常量,更易维护
        final String DB_URL = "jdbc:postgresql://localhost:5432/postgres";
        final String DB_USER = "postgres";
        final String DB_PWD = "root";
        // 定义批次大小,和fetchSize保持一致更合理
        final int BATCH_SIZE = 10000;

        try (Connection connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PWD);
             Statement st = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY,
                     ResultSet.CONCUR_READ_ONLY, ResultSet.FETCH_FORWARD);
             PreparedStatement pst = connection.prepareStatement(
                     "INSERT INTO contrato(contrato, codigo_postal,cups) VALUES(?, ?, ?)\n" +
                     "ON CONFLICT (contrato) DO\n" +
                     "UPDATE SET codigo_postal = excluded.codigo_postal, cups = excluded.cups")) {

            connection.setAutoCommit(false);
            // 关键:PostgreSQL需要显式设置fetchSize,驱动会按批次拉取数据
            st.setFetchSize(BATCH_SIZE);

            System.out.println("开始时间: " + new Date());
            System.out.println("执行查询...");
            ResultSet rs = st.executeQuery("SELECT * FROM contratacion");
            System.out.println("查询完成,开始处理数据...");

            int cont = 0;
            while (rs.next()) {
                cont++;
                Integer contrato = rs.getInt(1);
                Integer codigo_postal = rs.getInt(2);
                String cups = rs.getString(3);

                pst.setInt(1, contrato);
                pst.setInt(2, codigo_postal);
                pst.setString(3, cups);
                // 添加到批次,而非立即执行单条插入
                pst.addBatch();

                // 每达到批次大小,执行批量插入并提交事务
                if (cont % BATCH_SIZE == 0) {
                    pst.executeBatch();
                    connection.commit();
                    System.out.println("已处理 " + cont + " 行数据");
                }
            }

            // 处理最后一批不足BATCH_SIZE的数据
            if (cont % BATCH_SIZE != 0) {
                pst.executeBatch();
                connection.commit();
                System.out.println("已处理最后一批,总计 " + cont + " 行数据");
            }

            System.out.println("结束时间: " + new Date());
        } catch (SQLException ex) {
            // 不要空捕获异常,至少打印信息方便排查
            ex.printStackTrace();
            // 发生异常时回滚事务
            try (Connection conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PWD)) {
                conn.rollback();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

额外注意事项

  • PostgreSQL驱动的fetchSize强制配置:如果还是出现只读取部分数据的情况,可以在JDBC URL中添加useFetchSizeWithForwardOnly=true参数,强制驱动遵循fetchSize设置,比如:
    jdbc:postgresql://localhost:5432/postgres?useFetchSizeWithForwardOnly=true
    
  • 禁止空异常捕获:原代码里的空catch块会吞掉所有异常,导致你无法排查问题,一定要添加异常打印或日志记录。
  • 资源自动关闭:修正后的代码用了try-with-resources语法,确保Statement、PreparedStatement等资源自动关闭,避免内存泄漏。
  • 合理的事务提交时机:每批次提交一次事务,而非每行提交,既能大幅提升性能,也能避免频繁提交对ResultSet读取的干扰。

这样修改后,程序会按1万行的批次读取contratacion表的全部3000万行数据,同时批量插入到contrato表,不会出现提前终止的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:17:52