JDBC的statement对象,有一个setFetchSize方法,参数是一个int值,其作用是,执行查询时,一次从服务器端拿多少行的数据到本地jdbc客户端这里来
还有一个方法:setMaxRows,这个方法,作用是JDBC最多返回多少行数据给调用者。举个例子:
一个表,有100行数据,sql语句为select * from table,fetchsize设置为20,maxrows设置为50,则:
数据库服务器端在执行这个查询的时候,会在内部维护一个游标(hander),调用者在执行resultset.next()的时候,JDBC会先与服务器端进行通信,取20条数据到jdbc的客户端中,然后返回第一条给调用者,当调用者取到第21条数据的时候,又会触发jdbc到数据库服务端拿数据,又拿20条,返回第21条给调用者。。。当调用者要拿第41条数据的时候,jdbc这时不是去数据库服务器端拿20条了,而是10条,因为maxrows为50,最多取50,之前已经取出40条了,所以只剩10条可取。
为什么要设置fetchsize?显然,这样设置,就避免了服务器一下子把所有结果都塞到客户端来了,将客户端的内存资源给撑爆掉了。比如:一行数据就是100M大小,如果JDBC一下子取50条,就需要5G的内存,而每次取10条,那么,就只需要1G内存就可以了,客户端的资源将变的可控!
上面解释了fetchsize参数的作用,下面说下我遇到的坑:
在做postgresql的jdbc的一些测试的时候,发现fetchsize无论设置多少,都不生效,照样一次把所有的结果都从服务器端拿到本地内存中,然后返回给应用程序服务。。。。
没办法,就从https://github.com/pgjdbc 上将postgresql的jdbc的源代码拿下来研究了下,终于发现了一些蛛丝马迹:
org.postgresql.core.v3.QueryExecutorImpl的sendOneQuery方法中,有如下代码:
else if (!usePortal) { rows = maxRows; // Not using a portal -- fetchSize is irrelevant }
注释写的很清楚:当 !usePortal 时,要获取的行数,直接等于maxRows,而不考虑fetchSize这个值到底是多少,也就是说,fetchSize被直接忽略了。。。
那么,什么时候usePortal这个变量为false呢?(触发了fetchSize失效),继续看这个变量的赋值:
boolean usePortal = (flags & QueryExecutor.QUERY_FORWARD_CURSOR) != 0 && !noResults && !noMeta && fetchSize > 0 && !describeOnly;
这么多条件,只要一个成立,fetchSize就失效了:
!noResults表示这个sql不需要返回任何结果,这个肯定等于true,因为所有的select都会要求返回结果
!noMeta表示这个sql不需要返回元数据,这个肯定等于true,因为select都要求返回元数据,供后续的resultSet.get使用
!fetchSize大于0,这个不说了,自然是true
!describeOnly,这个只有在desc table这样的语句的时候,才会是false,对于select,也是true
那么,试下的唯一的可能导致usePortal为false的原因就是 flags & queryExecutor.QUERY_FORWARD_CURSOR这个值等于0了。。
继续往上翻,看看什么时候才不会执行flags = flags |QueryExecutor.QUERY_FORWARD_CURSOR 这个代码了,因为只有这个代码没有被执行过,才会导致上面这个条件为false
然后将代码定位到了AbstractJdbc2Statement类的execute方法:
// Enable cursor-based resultset if possible. if (fetchSize > 0 && !wantsScrollableResultSet() && !connection.getAutoCommit() && !wantsHoldableResultSet()) flags |= QueryExecutor.QUERY_FORWARD_CURSOR;
其中:wantsHoldableResultSet()代码直接返回的false,所以,不考虑这个,
那么,要么wantsScrollableResultSet()返回true,或者connection.getAutoCommit()返回true,才会导致flags不包含QueryExecutor.QUERY_FORWARD_CURSOR,才会导致fetchSize失效
wantsScrollableResultSet()这个方法的代码为:
protected boolean wantsScrollableResultSet() { return resultsettype != ResultSet.TYPE_FORWARD_ONLY; }
1、如果connection是自动提交事务的,那么,fetchSize将失效
2、如果statement不是TYPE_FORWARD_ONLY的,那么,fetchSize也将失效
结论:
如果想fetchSize生效,必须保证connection是autocommit = false的,并且,statement为forward_only的:
conn.setAutoCommit(false); final Statement statement = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,ResultSet.FETCH_FORWARD);
另外,不带参数的
conn.createStatement(),其默认就是TYPE_FORWARD_ONLY
所以,一般情况下,如果想fetchsize生效,必须设置autocommit为flase,也就是需要手工去管理事务。
再另外说下,在ORACLE的JDBC中,没这个坑,默认fetchsize为10:
https://docs.oracle.com/cd/E11882_01/java.112/e16548/resltset.htm#JJDBC28621
在MysqL的jdbc中,必须设置fetchsize为Integer.MIN_VALUE,这样JDBC就一行一行的从服务器端拿数据,它不支持其他大小的fetchsize:
http://stackoverflow.com/questions/20496616/fetchsize-in-resultset-set-to-0-by-default
备注:本次测试用的Postgresql的JDBC的版本为:
<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>9.3-1102-jdbc4</version> </dependency>