前言:

最近做数据同步,需要从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. unity3d asset store下载的代码所在位置

    Asset Store下载了官方的示例,却找不到了,又不想重新下载 PC:C:\Users\PCNAME\AppData\Roaming\Unity\Asset StoreMAC:"~/Li ...

  2. Html5文件

    HTML5 file api 读取文件MD5码 http://www.zhuwenlong.com/blog/52d6769f93dcae3050000003

  3. 实现SQL Server中的切割字符串SplitString函数,返回Table

    有时我们要用到批量操作时都会对字符串进行拆分,可是SQL Server中却没有自带Split函数,所以要自己来实现了. -- ===================================== ...

  4. SQL关于日期的查询

    SQL查询某天的记录: datediff(day,[Datetime],'2012-08-03')=0 把Datetime换为你的相应字段: SQL查询今天的记录: datediff(day,[Dat ...

  5. Ajax初步理解

    最近在项目中经常会使用Ajax技术,用法上倒是熟练了,但是只知其然,不知其所以然,抽时间读了读JavaScript高级程序设计中关于Ajax的介绍有了些初步的理解,在此总结一下. 什么是Ajax Aj ...

  6. Firefox SVG getBBox方法返回'NS_ERROR_FAILURE'错误分析

    在SVG中,我们无法给Text元素设置Width和Height属性,因此无法直接获取Text元素的高和宽.如果想要给Text元素添加背景色,最简单的办法就是在Text元素的下面添加Rect,然后给Re ...

  7. JSPatch中的OC高级语法

    1)多线程相关 dispatch_after dispatch_async dispatch_sync dispatch_get_main_queue dispatch_get_global_queu ...

  8. atitit.sql server2008导出导入数据库大的表格文件... oracle mysql

    atitit.sql server2008导出导入数据库大的表格文件... 1. 超过80M的文件是不能在查询分析器中执行的 1 2. Oracle ,mysql大的文件导入 1 2.1. 使用sql ...

  9. UC脱茧蜕变,移动资讯市场格局再生变

    日前,UC浏览器正式更名为UC,同时正式发布大数据驱动的独立资讯应用“UC头条”.而整个UC品牌也从工具类升级为优质资讯内容平台,并吹响了向“大数据新型媒体平台”进军的冲锋号.根据UC官方公布的数据显 ...

  10. JavaWeb学习总结(十二)——Session

    一.Session简单介绍 在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(session对象),注意:一个浏览器独占一个session对象(默认情况下).因此,在需要保存用户数据时,服务 ...