前言:

最近做数据同步,需要从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);
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参数相关,所以我设置了fetchSize,奇葩的是没有生效

第四步、sendQuery,sendOneQuery方法,在这里发现了问题,好在代码不太多,我就都贴出来了:

        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;

那么咱们逐一看一下这些条件:

  • !noResults表示这个SQL不需要返回任何结果,这个肯定等于true,因为所有的select都会要求返回结果
  • !noMeta表示这个SQL不需要返回元数据,这个肯定等于true,因为select都要求返回元数据,供后续的resultSet.get使用
  • !fetchSize大于0,这个不说了,自然是true
  • !describeOnly,这个只有在desc table这样的语句的时候,才会是false,对于select,也是true

那么,试下的唯一的可能导致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
}

至此,问题终于被定位:

1、如果connection不是自动提交事务的,那么,fetchSize将生效(非默认)

2、如果statement是TYPE_FORWARD_ONLY的,那么,fetchSize也将生效(默认)

结论

如果想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();
}
}
}

这次再一次执行,发现根本不卡。

感悟:类似这种问题都的慢慢跟踪代码,更重要的是身边需要有同事可以相互讨论,形成氛围,因为这个过程十分乏味,自己很难坚持下来。

参考文献

https://jdbc.postgresql.org/documentation/head/query.html

http://m.blog.csdn.net/blog/itjin45/42004447#

http://blog.csdn.net/hantiannan/article/details/4509167

Jdbc如何从PostgreSql读取海量数据?PostgreSql源代码分析纪录的更多相关文章

  1. PostgreSQL入门,PostgreSQL和mysql

    PostgreSQL被誉为“世界上功能最强大的开源数据库”,是以加州大学伯克利分校计算机系开发的POSTGRES 4.2为基础的对象关系型数据库管理系统. PostgreSQL支持大部分 SQL标准并 ...

  2. PostgreSQL CPU满(100%)性能分析及优化(转)

    PostgreSQL CPU满(100%)性能分析及优化 转自:https://help.aliyun.com/knowledge_detail/43562.html    在数据库运维当中,一个DB ...

  3. Mac安装postgresql和卸载PostgreSQL

    1.homebrew安装 brew install postgresql 2.初始化 initdb /usr/local/var/postgres 3.创建数据库及查看数据库 (1)先创建db. cr ...

  4. PostgreSQL——服务器配置_{postgresql.conf}

    一.设置参数 所有参数名称都是不区分大小写的 值为字符串时,需要单引号 值为数值时不需要单引号,但带单位时,需要单引号 配置文件(如:postgresql.conf.postgresql.auto.c ...

  5. MyBatis架构设计及源代码分析系列(一):MyBatis架构

    如果不太熟悉MyBatis使用的请先参见MyBatis官方文档,这对理解其架构设计和源码分析有很大好处. 一.概述 MyBatis并不是一个完整的ORM框架,其官方首页是这么介绍自己 The MyBa ...

  6. Hadoop源代码分析

    http://wenku.baidu.com/link?url=R-QoZXhc918qoO0BX6eXI9_uPU75whF62vFFUBIR-7c5XAYUVxDRX5Rs6QZR9hrBnUdM ...

  7. 《深入实践Spring Boot》阅读笔记之三:核心技术源代码分析

    刚关注的朋友,可以回顾前两篇文章: 基础应用开发 分布式应用开发 上篇文章总结了<深入实践Spring Boot>的第二部分,本篇文章总结第三部分,也是最后一部分.这部分主要讲解核心技术的 ...

  8. Hadoop源代码分析(完整版)

    Hadoop源代码分析(一) 关键字: 分布式云计算 Google的核心竞争技术是它的计算平台.Google的大牛们用了下面5篇文章,介绍了它们的计算设施. GoogleCluster:http:// ...

  9. android-plugmgr源代码分析

    android-plugmgr是一个Android插件加载框架,它最大的特点就是对插件不需要进行任何约束.关于这个类库的介绍见作者博客,市面上也有一些插件加载框架,但是感觉没有这个好.在这篇文章中,我 ...

随机推荐

  1. jeesite笔记

    环境 Github上的不能初始化数据库:https://github.com/thinkgem/jeesite 官网上的可以: http://jeesite.com/ 用 Idea 打开,修改 \sr ...

  2. SQL Server转发记录指针的坏味道

    什么是转发记录指针? 转发记录指针是堆表中特有的数据存储机制. 当你修改了某个数据页中的一行时,如果该行所在的数据页已经无法存放其修改后的行, SQL Server会把这行数据移动到一个新的数据页上面 ...

  3. Nginx运行Mono Web (ASP.NET)

    Mono Web除了可以使用Apache/mod_mono方式承载运行外,还可以使用Nginx/FastCGI方式运行. Nginx配置asp.net更简单方便,用处也多,可以通过FastCGI运行a ...

  4. Linux:用户&用户组操作

    用户:查看用户:whoami.who am i.who mom likes创建用户:adduser dgw切换登录用户:su -l dgw退出当前用户: exit删除用户:deluser dgw -- ...

  5. SVN中Branch的创建与合并

    在使用源代码版本控制工具时,最佳实践是一直保持一个主干版本.但是为了应付实际开发中的各种情况,适时的开辟一些分支也是很有必要的.比如在持续开发新功能的同时,需要发布一个新版本,那么就需要从开发主干中建 ...

  6. Effective STL中文版 译序

    <Effective STL中文版>译序 就像本书的前两本姊妹作(Effective C++.More Effective C++)一样,本书的侧重点仍然在于提升读者的经验,只不过这次将焦 ...

  7. javaweb学习总结(二十五)——jsp简单标签开发(一)

    一.简单标签(SimpleTag) 由于传统标签使用三个标签接口来完成不同的功能,显得过于繁琐,不利于标签技术的推广, SUN公司为降低标签技术的学习难度,在JSP 2.0中定义了一个更为简单.便于编 ...

  8. Java 线程 — synchronized、volatile、锁

    线程同步基础 synchronized 和volatile是Java线程同步的基础. synchronized 将临界区的内容上锁,同一时刻只有一个进程能访问该临界区代码 使用的是内置锁,锁一个时刻只 ...

  9. Android 使WebView支持HTML5 Video(全屏)播放的方法

    http://blog.csdn.net/zrzlj/article/details/8050633  1)需要在AndroidManifest.xml文件中声明需要使用HardwareAcceler ...

  10. maven继承parent,relativePath warn信息的解决办法

    往下看之前一定要先看 %MAVEN_HOME%/conf/settings.xml 配置文件的是否更改了,是否配置正确 <mirror> <id>nexus</id> ...