Statement.setFetchSize(nSize)方法在SQL Server JDBC驱动程序中的作用是什么?

时间:2009-08-23 11:34:32

标签: java sql-server jdbc

我有这个非常大的桌子,每天有数百万条记录,每天结束时我都会提取前一天的所有记录。我这样做:

String SQL =  "select col1, col2, coln from mytable where timecol = yesterday";
Statement.executeQuery(SQL);

问题是这个程序需要2GB的内存,因为它会将所有结果存储在内存中然后处理它。

我尝试设置Statement.setFetchSize(10)但它从操作系统获取完全相同的内存它没有任何区别。我正在使用 Microsoft SQL Server 2005 JDBC驱动程序

有没有办法以小块的形式读取结果,比如Oracle数据库驱动程序执行查询时只显示几行,当你向下滚动时会显示更多结果?

8 个答案:

答案 0 :(得分:61)

在JDBC中,setFetchSize(int)方法对JVM中的性能和内存管理非常重要,因为它控制从JVM到数据库的网络调用次数,以及相应的用于ResultSet处理的RAM量。

如果正在调用setFetchSize(10)并且驱动程序忽略它,那么可能只有两个选项:

  1. 尝试使用其他符合fetch-size提示的JDBC驱动程序。
  2. 查看Connection上的驱动程序特定属性(创建Connection实例时的URL和/或属性映射)。
  3. RESULT-SET是响应查询而在数据库上编组的行数。 ROW-SET是从JVM到DB的每次调用从RESULT-SET中取出的行块。 处理所需的这些调用数和生成的RAM取决于获取大小设置。

    因此,如果RESULT-SET有100行且fetch-size为10, 将在任何给定时间使用大约10 * {行内容大小} RAM来检索所有数据的10个网络调用。

    默认的fetch-size是10,相当小。 在发布的情况下,似乎驱动程序忽略了fetch-size设置,在一次调用中检索所有数据(大RAM要求,最佳最小网络调用)。

    ResultSet.next()下面发生的事情是它实际上并没有从RESULT-SET一次获取一行。它从(本地)ROW-SET中获取它,并在本地客户端上耗尽时从服务器获取下一个ROW-SET(不可见)。

    所有这一切都取决于驱动程序,因为设置只是一个“提示”,但实际上我发现它是如何适用于许多驱动程序和数据库(在许多版本的Oracle,DB2和MySQL中验证)。

答案 1 :(得分:25)

fetchSize参数是JDBC驱动程序的提示,用于从数据库中一次获取的许多行。但是司机可以自由地忽略这一点并做它认为合适的事情。某些驱动程序(如Oracle的驱动程序)以块的形式获取行,因此您可以读取非常大的结果集而无需大量内存。其他驱动程序只需一次性读取整个结果集,我猜这就是你的驱动程序在做什么。

您可以尝试将驱动程序升级到SQL Server 2008版本(可能更好)或开源jTDS驱动程序。

答案 2 :(得分:14)

您需要确保Connection上的自动提交已启用关闭,否则setFetchSize将无效。

dbConnection.setAutoCommit(false);

编辑:记得当我使用此修补程序时,它特定于Postgres,但希望它仍适用于SQL Server。

答案 3 :(得分:4)

语句界面Doc

  

摘要:void setFetchSize(int rows)   为JDBC驱动程序提供了一个提示   应该获取的行数   从更多行的数据库   需要的。

阅读此电子书J2EE and beyond By Art Taylor

答案 4 :(得分:3)

听起来像mssql jdbc正在为你缓冲整个结果集。您可以添加一个连接字符串参数,说明selectMode = cursor或responseBuffering = adaptive。如果您使用的是2005 mssql jdbc驱动程序的2.0+版本,那么响应缓冲应默认为自适应。

http://msdn.microsoft.com/en-us/library/bb879937.aspx

答案 5 :(得分:1)

在我看来,确实希望限制查询和页面中返回的行数。如果是这样,您可以执行以下操作:

select * from (select rownum myrow, a.* from TEST1 a )
where myrow between 5 and 10 ;

你必须确定自己的界限。

答案 6 :(得分:1)

试试这个:

String SQL = "select col1, col2, coln from mytable where timecol = yesterday";

connection.setAutoCommit(false);
PreparedStatement stmt = connection.prepareStatement(SQL, SQLServerResultSet.TYPE_SS_SERVER_CURSOR_FORWARD_ONLY, SQLServerResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(2000);

stmt.set....

stmt.execute();
ResultSet rset = stmt.getResultSet();

while (rset.next()) {
    // ......

答案 7 :(得分:1)

我在项目中遇到了完全相同的问题。问题在于,即使提取大小可能足够小,JDBCTemplate也会读取查询的所有结果,并将其映射到一个巨大的列表中,这可能会破坏您的内存。我最终扩展了NamedParameterJdbcTemplate以创建一个返回Object of Stream的函数。该Stream基于通常由JDBC返回的ResultSet,但仅在Stream需要时才从ResultSet中提取数据。如果您没有保留对此Stream流的所有Object的引用,这将起作用。我确实在org.springframework.jdbc.core.JdbcTemplate #execute(org.springframework.jdbc.core.ConnectionCallback)的实现上激励了我自己。唯一真正的区别与如何处理ResultSet有关。我最终编写了这个函数来包装ResultSet:

private <T> Stream<T> wrapIntoStream(ResultSet rs, RowMapper<T> mapper) {
    CustomSpliterator<T> spliterator = new CustomSpliterator<T>(rs, mapper, Long.MAX_VALUE, NON-NULL | IMMUTABLE | ORDERED);
    Stream<T> stream = StreamSupport.stream(spliterator, false);
    return stream;
}
private static class CustomSpliterator<T> extends Spliterators.AbstractSpliterator<T> {
    // won't put code for constructor or properties here
    // the idea is to pull for the ResultSet and set into the Stream
    @Override
    public boolean tryAdvance(Consumer<? super T> action) {
        try {
            // you can add some logic to close the stream/Resultset automatically
            if(rs.next()) {
                T mapped = mapper.mapRow(rs, rowNumber++);
                action.accept(mapped);
                return true;
            } else {
                return false;
            }
        } catch (SQLException) {
            // do something with this Exception
        }
    }
}

你可以添加一些逻辑来使Stream“自动关闭”,否则不要忘记在完成后关闭它。