[原] Oracel JDBC 内存 问题

About Oracle JDBC memory

[本文原创链接: http://www.smithfox.com/?e=153 , 转载请保留此声明, 谢谢 ]

Oracle JDBC 驱动, 在大数据量查询时会使用大量的内存, 如果控制不好很容易导致Java出现Out of memory错误.

耗内存有多方面的原因: 主要包括五个方面: (下面的讨论全部基于我自己的环境: ojdbc6.jar, version是11.1.0.7.0-Production, Oracle 10G, Java6(JDBC4) )

1. Oracle JDBC在从后台取数据时, 为每行所设置的 buffer 是以列的最大size来计算分配内存的.

比如:

        varchar2        = 4000 bytes; //没有定义size, 就取最大值

        varchar2(200) = 200*2 = 400 bytes

        Number(x,x) = 22 bytes//固定的

        Clob/Blob/NClob = 4000 bytes //初始化为这个大小

下面是一些分析过程:

反编译 oracle ojdbc6.jar 后的代码, 大概可以找到一些线索:

oracle.jdbc.driver.Accessor 这个虚基类中定义了一些值得注意的成员变量:

int internalType;
int internalTypeMaxLength;

byte[] rowSpaceByte = null;
char[] rowSpaceChar = null;

int byteLength = 0;
int charLength = 0;

再从各个不同数据类型的子Accessor类, 可以看到 byteLength或是 charLength的初始化:

比如在 NumberCommonAccessor中:

void initForDataAccess(int paramInt1, int paramInt2, String paramString) throws SQLException
{
     if (paramInt1 != 0) {
       this.externalType = paramInt1;
     }
     this.internalTypeMaxLength = 21;
 
     if ((paramInt2 > 0) && (paramInt2 < this.internalTypeMaxLength)) {
       this.internalTypeMaxLength = paramInt2;
     }
     //所以NUMBER类型的初始化buffer的lenght是 22 字节
     this.byteLength = (this.internalTypeMaxLength + 1);
   }
}

下面是我根据反编译的代码罗列各个Accessor的 byteLength(仅作参考)

 oracle.jdbc.driver.XXXXAccessor  internalTypeMaxLength (byte)  byteLength
 BlobAccessor  this.internalTypeMaxLength = 4000;  
 ClobAccessor  this.internalTypeMaxLength = 4000;  
 RowidAccessor  this.internalTypeMaxLength = 128;  
 IntervaldsAccessor  this.internalTypeMaxLength = 11;  
 IntervalymAccessor  this.internalTypeMaxLength = 5;  
 DateAccessor  this.internalTypeMaxLength = 7;  
 BfileAccessor  this.internalTypeMaxLength = 530;  
 BinaryDoubleAccessor  this.internalTypeMaxLength = 8;  
 BinaryFloatAccessor  this.internalTypeMaxLength = 4;  
 RawAccessor  this.internalTypeMaxLength = 2000;   + 2
 TimestampAccessor  this.internalTypeMaxLength = 11;  
 NumberCommonAccessor  this.internalTypeMaxLength = 21;   + 1=22
     

charLength的设置:

结合 VarcharAccessor和 CharCommonAccessor两个类, 初始化charLength代码如下: (是伪代码, 已经将很多其它的和charLength相关的逻辑分支去掉了, 只留了计算最大值的代码):

VarcharAccessor(xxxxx) throws SQLException
{
     int i = 4000;

     if (paramOracleStatement.sqlKind == 1) {
       i = 32766;
     }

	this.internalMaxLengthNewer = i;
	this.internalMaxLengthOlder = 2000;

	if (this.statement.connection.getVersionNumber() >= 8000) {
		   this.internalTypeMaxLength = this.internalMaxLengthNewer;
	} else {
		  this.internalTypeMaxLength = this.internalMaxLengthOlder;
	}

	this.charLength = (this.internalTypeMaxLength + 1);
}

 但因为 varchar2 在定义表结构的时候是可以指定 size的, 所以实际中, JDBC都会根据 size大小来计算buffer size, 不会象其它的数据类型,直接取最大值.

通过网上的一些资料: varchar2类型的JDBC buffer 初始化为: size*2

无意中还发现一个 Oracle 的 function  vsize, 使用方法如下:

select x, y, vsize(x), vsize(y) from t;

可以返回当前值的所占用的byte大小, vsize 的根据可以参考 Oracle 的这篇文章:

http://download.oracle.com/docs/cd/B10501_01/server.920/a96524/c13datyp.htm

比如当中就提到 NUMBER 类型字节数的评估方法: ROUND((length(p)+s)/2))+1  //p=precision, s=scale

不过 vsize 不能作为JDBC的buffer size的参考, 因为JDBC是取最大值的.

由些可见, 作为一个数据库表的设计者, 在定义表的列类型时, 不能随便: 应该遵循最小使用原则, 否则会数据库的使用者带来不少的麻烦.

比如能用varchar2的就不要用clob, 用varchar2时不要随便定义一个很大的size, 比如 username varchar(1024), username是不可能定义为这么长的.

如果很不幸, 你在使用 Oracle JDBC时频繁遇到 Out of Memory问题, 并且数据库表的设计者就是这么定义的, 你可以根据业务规则来重新定义 column, 告诉 JDBC driver, 这个列其实没有那么大:

OraclePreparedStatement.defineColumnType(column_index, typeCode, max_size); 通过重新设置max_size能起到让JDBC少使用一点buffer. 不过这个有将业务字段截断的风险, 必须结合业务的确认才能做.

LOB字段默认是 4000 bytes, 如果你不想让它这么浪费, 可以通过 oracle.jdbc.OracleStatement.setLobPrefetchSize(int) 来重新设置, 这个没有截断的问题.

2. fetchsize设置

上面花了大量的篇幅讲, JDBC为每一行是怎么分配buffer的, 而fetchsize则是告诉JDBC要cache多少行.

所以需要的 memory = fetchsize * rowbuffer.

可见fetchsize太大了, 必然会消耗很多的内存. 但是太小了又会影响查询性能, 这个必须要结合数据库服务器, 网络, 业务需求, 来达到一个平衡. 一般来说 大于5000后, 通过fetchsize已经达不能提高性能的目的了.(不可迷信, 我是自己环境的测试).  对于内存消耗来说, 5000倍已经是一个很大的值了, 2000是一个可以接受的平衡值.(自己的经验值, 还是要结合实际情况做测试).

3. 新的JDBC标准(不记得是3还是4了), 增加了statement client pool功能.

对于一个处理业务的JDBC程序, 这个功能肯定是一个很好的, 以内存换速度的方法. 不过毕竟不是所有的JDBC使用程序都是这样的, 我的程序是不关心业务只关心数据, 所以对我的程序来说, cache statement除了浪费我的内存外, 没有其它的什么好处. OK, 那我们将其关掉吧, 下面是我收集的一些开关, 管它哪个有用, 通通关上:

Properties props = new Properties();
props.put("user", dbProfile.getUser()); 
props.put("password", dbProfile.getPassword()); 
props.put("defaultBatchValue","1");//不用batch update功能

// If “true” the buffers are freed when a PreparedStatement is cached. 
//Note that setting freeMemoryOnEnterImplicitCache does not cause the parameter value buffers to be released, only the row data buffers
props.put("oracle.jdbc.freeMemoryOnEnterImplicitCache","true");
props.put("oracle.jdbc.maxCachedBufferSize","0");
props.put("remarksReporting","false");
props.put("PreparedStatementCacheSize","0");

Connection conn = DriverManager.getConnection(
dbProfile.getUrl(),props);

OracleConnection oraconn = (OracleConnection)conn;
//控制statement cache的大小
oraconn.setImplicitCachingEnabled(false);
oraconn.setExplicitCachingEnabled(false);
oraconn.setStatementCacheSize(0);

PreparedStatement stmt1=xxx;
stmt1.setPoolable(false);
((OraclePreparedStatement)stmt1).setDisableStmtCaching(true);

4. JVM最大内存设置

另外的一个就是JVM启动时最大内存也不能太小了.

有一个到现在可能还误导的问题: 就是jvm最大内存设置不能超过4G, 很显然这个是32bit的jvm和OS的问题, 如果你是64bit OS加上64bit的JVM当然没有这个限制了.

5. Resultset类型

根据网友 http://chilli.iteye.com/ 的提醒, 将Resultset设置成如下, 也可以减少潜在可能的内在浪费:

stmt1 = this._conn.prepareStatement(sql,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
stmt1.setFetchDirection(ResultSet.FETCH_FORWARD);

最后附几个有用的文章和链接:

http://xulingbo.net/?p=64

http://www.oracle.com/technetwork/database/enterprise-edition/memory.pdf

http://alumnus.caltech.edu/~chamness/JDBC_Tuning.pdf

http://space.itpub.net/25105315/viewspace-694451

http://download.oracle.com/docs/cd/B28359_01/java.111/b31224/toc.htm

[本文原创链接: http://www.smithfox.com/?e=153 , 转载请保留此声明, 谢谢 ]

smithfox | Friday 24 June 2011 at 11:53 am | | Java        | Used tags: , , , ,

No comments

(optional field)
(optional field)
为阻止垃圾广告, 请在提交评论前, 回答一个简单问题(Please answer an simple question)
Remember personal info?
Notify
Small print: All html tags except <b> and <i> will be removed from your comment. You can make links by just typing the url or mail-address.