2015-01-27 65 views
4

使用Oracle java JDBC(ojdbc14 10.2.x),加载具有多行的查询需要永久(高延迟环境,这显然是Oracle JDBC中的默认预取是默认大小“10”,这要求每10行的往返时间一次。我试图建立一个积极的预取大小,以避免这一点。Oracle JDBC预取:如何避免RAM耗尽

PreparedStatement stmt = conn.prepareStatement("select * from tablename"); 
statement.setFetchSize(10000); 
ResultSet rs = statement.executeQuery(); 

这可以工作,而是我得到一个内存不足的异常。我有假设setFetchSize会告诉它在进入时缓冲“那么多行”,并使用每行所需的RAM数量。如果我使用50个线程运行,即使是16G的-XMX空间,它也会耗尽内存。像泄漏:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space 
    at java.lang.reflect.Array.newArray(Native Method) 
    at java.lang.reflect.Array.newInstance(Array.java:70) 
    at oracle.jdbc.driver.BufferCache.get(BufferCache.java:226) 
    at oracle.jdbc.driver.PhysicalConnection.getCharBuffer(PhysicalConnection.java:7422) 
    at oracle.jdbc.driver.OracleStatement.prepareAccessors(OracleStatement.java:983) 
    at oracle.jdbc.driver.T4CTTIdcb.receiveCommon(T4CTTIdcb.java:273) 
    at oracle.jdbc.driver.T4CTTIdcb.receive(T4CTTIdcb.java:144) 
    at oracle.jdbc.driver.T4C8Oall.readDCB(T4C8Oall.java:771) 
    at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:346) 
    at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:186) 
    at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:521) 
    at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:205) 
    at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:861) 
    at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1145) 
    at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1267) 
    at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3449) 
    at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3493) 
    at oracle.jdbc.driver.OraclePreparedStatementWrapper.executeQuery(OraclePreparedStatementWrapper.java:1491) 
    .... 

我能做些什么仍然可以预取但不会耗尽内存?到底是怎么回事?

上SO与最接近相关的项目是这样的:https://stackoverflow.com/a/14317881/32453

回答

5

基本上,后来ojdbc罐子Oracle的默认策略是每个“预取”一行可容纳的最大尺寸可以想到,返回到“预分配”的数组来自该查询。所以在我的情况下,我有一些VARCHAR2(4000)在那里,所以50个线程* 3列varchar2的* 4000加起来超过千兆字节的RAM [yikes]。似乎没有选择说“不要预先分配该数组,只需使用所需的大小”。 Ojdbc甚至将这些预分配缓冲区保留在之间的准备状态之间,以便它可以重用它们。绝对是一个记忆猪。

解决的办法是确定最大实际列大小,然后替换查询(假设50是最大大小)select substr(column_name, 0, 50)以及配置文件,并且只使用setFetchSize的最高值作为实际显着的速度改进。

你可以做的其他事情:减少预取行数,增加Xmx参数,只选择你需要的列。一旦我们能够使用至少预取400 [确保配置文件查看哪些数字对您有好处,并且具有高延迟,我们看到所有查询的预取大小都提高了3-4K],则性能显着提高。

我想如果你想对稀疏的“非常长”的行进行真正的攻击,你可能能够在你遇到这些[稀有]行时重新查询。

详情ad nauseum here