使用MySQLstream式传输大型结果集

我正在开发一个使用大型MySQL表的Spring应用程序。 加载大型表时,我得到一个OutOfMemoryException ,因为驱动程序试图将整个表加载到应用程序内存中。

我试过使用

 statement.setFetchSize(Integer.MIN_VALUE); 

但是然后每个ResultSet我打开close() ; 在线寻找我发现发生这种情况是因为它会在closuresResultSet之前加载任何未读的行,但事实并非如此,因为我这样做:

 ResultSet existingRecords = getTableData(tablename); try { while (existingRecords.next()) { // ... } } finally { existingRecords.close(); // this line is hanging, and there was no exception in the try clause } 

挂起发生的小表(3行)以及如果我不closuresRecordSet(发生在一个方法),然后connection.close()挂起。


堆栈跟踪:

SocketInputStream.socketRead0(FileDescriptor,byte [],int,int,int)行:不可用[native方法]
SocketInputStream.read(byte [],int,int)行:129
ReadAheadInputStream.fill(int)行:113
ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(byte [],int,int)行:160
ReadAheadInputStream.read(byte [],int,int)行:188
MysqlIO.readFully(InputStream,byte [],int,int)行:2428 MysqlIO.reuseAndReadPacket(Buffer,int)行:2882
MysqlIO.reuseAndReadPacket(缓冲区)行:2871
MysqlIO.checkErrorPacket(int)行:3414
MysqlIO.checkErrorPacket()行:910
MysqlIO.nextRow(Field [],int,boolean,int,boolean,boolean,boolean,Buffer)行:1405
RowDataDynamic.nextRecord()行:413
RowDataDynamic.next()行:392 RowDataDynamic.close()行:170
JDBC4ResultSet(ResultSetImpl).realClose(boolean)行:7473 JDBC4ResultSet(ResultSetImpl).close()行:881 DelegatingResultSet.close()行:152
DelegatingResultSet.close()行:152
DelegatingPreparedStatement(DelegatingStatement).close()行:163
(这是我的课)Database.close()行:84

只设置获取大小不是正确的方法。 Statement#setFetchSize()的javadoc已经声明如下:

为JDBC驱动程序提供关于应从数据库中提取的行数的提示

司机实际上可以自由申请或忽略提示。 一些驱动程序忽略它,一些驱动程序直接应用它,一些驱动程序需要更多参数 MySQL JDBC驱动程序属于最后一类。 如果您检查MySQL JDBC驱动程序文档 ,您将看到以下信息(向下滚动2/3直到ResultSet头):

要启用此function,您需要按以下方式创build一个Statement实例:

 stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); stmt.setFetchSize(Integer.MIN_VALUE); 

请阅读文档的整个部分,它也描述了这种方法的注意事项。 这里有一个相关的引用:

这种方法有一些注意事项。 您必须先阅读结果集中的所有行(或closures它),然后才能在连接上发出任何其他查询,否则将引发exception。

(……)

如果语句在事务范围内,那么当事务完成时(这意味着语句需要首先完成),锁被释放。 与大多数其他数据库一样,只有在读取语句上的所有结果或者语句的活动结果集closures之后,语句才会完成。

如果这不能解决OutOfMemoryError (而不是Exception ),那么问题可能是你将所有的数据存储在Java的内存中,而不是立即处理它,这就需要在你的代码中进行更多的修改,也许是一个完整的重写。 在这之前我已经回答了类似的问题。

不要closures你的ResultSet两次。

显然,在closuresStatement它会尝试closures相应的ResultSet ,就像您在堆栈跟踪中的这两行中所看到的那样:

DelegatingResultSet.close()行:152
DelegatingPreparedStatement(DelegatingStatement).close()行:163

我以为挂起在ResultSet.close()但实际上是在调用ResultSet.close() Statement.close() ResultSet.close() 。 由于ResultSet已经closures,它只是挂起。

我们用results.getStatement().close()replace了所有的ResultSet.close() ,并删除了所有的Statement.close() ,现在问题就解决了。

如果有人遇到同样的问题,我通过在查询中使用LIMIT子句来解决这个问题。

这个问题被报告给MySql作为一个bug(在这里findhttp://bugs.mysql.com/bug.php?id=42929 ),现在它的状态是“没有bug”。 最相关的部分是:

目前没有办法在“中游”结束结果集

由于必须读取所有行,因此必须使用WHERE或LIMIT这样的子句来限制查询结果。 或者,请尝试以下操作:

 ResultSet rs = ... while(rs.next()) { ... if(bailOut == true) { break; } } while(rs.next()); // This will deplete the remaining rows on the stream rs.close(); 

这可能不是理想的,但至less它让你过去了。

如果您使用的是spring jdbc,那么您需要使用preparedstatement创build者与SimpleJdbcTemplate一起将fetchSize设置为Integer.MIN_VALUE。 它在这里描述http://neopatel.blogspot.com/2012/02/mysql-jdbc-driver-and-streaming-large.html

它挂起,因为即使你停止听,请求仍然继续。 为了以正确的顺序closuresResultSet和Statement,请先调用statement.cancel():

 public void close() { try { statement.cancel(); if (resultSet != null) resultSet.close(); } catch (SQLException e) { // ignore errors on closing } finally { try { statement.close(); } catch (SQLException e) { // ignore errors on closing } finally { resultSet = null; statement = null; } } }