PostgreSQL JDBC 源码分析之fetchSize
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> (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |