使用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; } } }