Java读取3000万行PostgreSQL表:fetchSize设置后仅获首1万行的解决方法
解决PostgreSQL JDBC按批次读取3000万行并批量插入的问题
看起来你遇到的问题主要有两个核心点:一是错误的批量插入方式导致你误以为只读取了前1万行,二是PostgreSQL JDBC驱动对fetchSize的特殊处理需要额外注意。咱们一步步来修正你的代码:
问题分析
- 每次循环单条执行+立即提交:你在
while(rs.next())里每次都调用pst.executeUpdate()和connection.commit(),这不仅效率极低,而且频繁的提交操作可能干扰ResultSet的批次读取逻辑,导致提前终止。 - PostgreSQL JDBC对fetchSize的要求:即使你设置了
st.setFetchSize(10000),PostgreSQL驱动默认会忽略TYPE_FORWARD_ONLYResultSet的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
相关产品推荐
相关产品推荐

