如何从PostgreSql读取海量数据?
发布日期:2016-4-26 16:4:18
一、前言: 近日在做数据同步,需要从PostgreSql获取数据,发现一旦数据比较多的时候,那么读取的速度非常慢,且内存占用特别多&GC不掉,不像mysql。 二、代码样例: 我为了方便讲解,下面所示的事例代码,从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); 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执行代码,发现卡死,并且占用大量的内存,如图1所示: 图1 四、解决方案: 我决定开始逐步调试,跟踪代码:
boolean usePortal = (flags & 8) != 0 && !noResults && !noMeta && fetchSize > 0 && !describeOnly; boolean oneShot = (flags & 1) != 0 && !usePortal; int rows; if(noResults) { rows = 1; } else if(!usePortal) { rows = maxRows; } else if(maxRows != 0 && fetchSize > maxRows) { rows = maxRows; } else { rows = fetchSize; } 所以是usePortal是true,那么fetchSize才会生效。 boolean usePortal = (flags & 8) != 0 && !noResults && !noMeta && fetchSize > 0 && !describeOnly; 现在看一下这些条件:
试下的唯一的可能导致usePortal为true的原因就是 flags & 8这个值是true。。(我想说这种写法很别致,tmd,设置flags的时候肯定是flags=flag|8,后来发现新的驱动修改了这种写法) 继续往上翻,看看什么时候才会执行flags = flags | 8 这个代码了,因为只有这个代码被执行过,才会导致上面这个条件为true,代码如下所示: if(this.fetchSize > 0 && !this.wantsScrollableResultSet() && !this.connection.getAutoCommit() && !this.wantsHoldableResultSet()) { flags |= 8; } 其中:wantsHoldableResultSet()代码直接返回的false,因此,不考虑这个。 那么,wantsScrollableResultSet()返回false,且connection.getAutoCommit()返回false,才会导致fetchSize生效。wantsScrollableResultSet()这个方法的代码为: protected boolean wantsScrollableResultSet() { return resultsettype != 1003; //老代码,看到这里我真想死,1003是啥?好在偶然的机会看见了新的Postgresql驱动,使用ResultSet.TYPE_FORWARD_ONLY表示1003} 至此,我的问题终于被定位:
五、结论 若想fetchSize生效,要保证connection是autocommit = false的,而且statement为1003(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,也就是需要手工去管理事务。默认的源代码如下: public Statement createStatement() throws SQLException { return this.createStatement(1003, 1007); //有兴趣的同学可以继续跟踪看看,1003就是resultsettype } 六、代码: 后来修改的代码如下所示: 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/datasource", "username", "password"); 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(); } } } 这次再一次执行,发现根本不卡。 感悟:类似这种问题都的慢慢跟踪代码,更重要的是身边需要有同事可以相互讨论,形成氛围,因为这个过程十分乏味,自己很难坚持下来。 七、参考文献
后面会更新一些mysql的问题,关注mysql的敬请期待。 上一条: 学习Redis 下一条: MySQL 5.7 GA版本正式发布
|