标签:
最近做数据同步,需要从PostgreSql获取数据,发现一旦数据比较多,那么读取的速度非常慢,并且内存占用特别多&GC不掉。
为了方便讲解,下面写了事例代码,从b2c_order获取数据,这个数据表6G左右。
package com.synchro; import java.sql.*; /** * Created by qiu.li on 2015/10/16. */ public class Test { public static void main(String[] args) { Connection conn = null; try { Class.forName("org.postgresql.Driver"); conn = DriverManager.getConnection("jdbc:postgresql://***.qunar.com:5432/database", "username", "password"); String sql = "select * from mirror.b2c_order"; PreparedStatement ps = conn.prepareStatement(sql); ps.setMaxRows(1000); ResultSet rs = ps.executeQuery(); int i = 0; while (rs.next()) { i++; if (i % 100 == 0) { System.out.println(i); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } }
在Idea执行代码,发现卡死,并且占用大量的内存
然后我决定开始逐步调试,跟踪代码:
第一步、我发现是在执行executeQuery方法的时候卡住的
第二步、是在执行AbstractJdbc2Statement.executeWithFlags方法卡住的
第三步、继续跟踪,并在网络上查看可能引起的原因是和设置fetchSize参数相关,所以继续跟踪
第四步、sendQuery,sendOneQuery方法,在这里发现了问题,好在代码不太多,我就都贴出来了:
boolean usePortal = (flags & QueryExecutor.QUERY_FORWARD_CURSOR) != 0 && !noResults && !noMeta && fetchSize > 0 && !describeOnly;// Work out how many rows to fetch in this pass. int rows; if (noResults) { rows = 1; // We‘re discarding any results anyway, so limit data transfer to a minimum } else if (!usePortal) { rows = maxRows; // Not using a portal -- fetchSize is irrelevant } else if (maxRows != 0 && fetchSize > maxRows) { rows = maxRows; // fetchSize > maxRows, use maxRows (nb: fetchSize cannot be 0 if usePortal == true) } else { rows = fetchSize; // maxRows > fetchSize }
可见是usePortal参数在影响Jdbc从数据库获取数据的行数,具体的原因参考:http://m.blog.csdn.net/blog/itjin45/42004447#
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,也就是需要手工去管理事务。
那么修改代码如下:
package com.synchro; import java.sql.*; /** * Created by qiu.li on 2015/10/16. */ public class Test { public static void main(String[] args) { Connection conn = null; try { Class.forName("org.postgresql.Driver"); conn = DriverManager.getConnection("jdbc:postgresql://l-tdata2.tkt.cn6.qunar.com:5432/log_analysis", "tkt_data_dev", "23eadc16-a4e4-418c-b18a-ccb2a6b9d587"); conn.setAutoCommit(false); //并不是所有数据库都适用,比如hive就不支持,orcle不需要 String sql = "select * from mirror.b2c_order"; PreparedStatement ps = conn.prepareStatement(sql); ps.setFetchSize(1000); //每次获取1万条记录 //ps.setMaxRows(1000); ResultSet rs = ps.executeQuery(); int i = 0; while (rs.next()) { i++; if (i % 100 == 0) { System.out.println(i); } } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } }
这次再一次执行,发现根本不卡。
https://jdbc.postgresql.org/documentation/head/query.html
http://m.blog.csdn.net/blog/itjin45/42004447#
Jdbc如何快速从PostgreSql获取大量数据,内存不被撑破
标签:
原文地址:http://www.cnblogs.com/liqiu/p/4885825.html