我有这个非常大的桌子,每天有数百万条记录,每天结束时我都会提取前一天的所有记录。我这样做:
String SQL = "select col1, col2, coln from mytable where timecol = yesterday";
Statement.executeQuery(SQL);
问题是这个程序需要2GB的内存,因为它会将所有结果存储在内存中然后处理它。
我尝试设置Statement.setFetchSize(10)
但它从操作系统获取完全相同的内存它没有任何区别。我正在使用 Microsoft SQL Server 2005 JDBC驱动程序。
有没有办法以小块的形式读取结果,比如Oracle数据库驱动程序执行查询时只显示几行,当你向下滚动时会显示更多结果?
答案 0 :(得分:61)
在JDBC中,setFetchSize(int)
方法对JVM中的性能和内存管理非常重要,因为它控制从JVM到数据库的网络调用次数,以及相应的用于ResultSet处理的RAM量。
如果正在调用setFetchSize(10)并且驱动程序忽略它,那么可能只有两个选项:
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驱动程序提供了一个提示 应该获取的行数 从更多行的数据库 需要的。
答案 4 :(得分:3)
听起来像mssql jdbc正在为你缓冲整个结果集。您可以添加一个连接字符串参数,说明selectMode = cursor或responseBuffering = adaptive。如果您使用的是2005 mssql jdbc驱动程序的2.0+版本,那么响应缓冲应默认为自适应。
答案 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“自动关闭”,否则不要忘记在完成后关闭它。