PostgreSQL JDBC 源码分析之fetchSize

来源:互联网 发布:网络散布谣言怎么定罪 编辑:程序博客网 时间:2024/04/29 17:10

JDBCstatement对象,有一个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>







0 0