Jdbc如何從PostgreSql讀取海量數據?PostgreSql源代碼分析紀錄

前言:

最近作數據同步,須要從PostgreSql獲取數據,發現一旦數據比較多,那麼讀取的速度很是慢,而且內存佔用特別多&GC不掉。html

代碼樣例:

爲了方便講解,下面寫了事例代碼,從b2c_order獲取數據,這個數據表6G左右。java

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執行代碼,發現卡死,而且佔用大量的內存sql

解決方案:

而後我決定開始逐步調試,跟蹤代碼:數據庫

第一步、我發現是在執行executeQuery方法的時候卡住的網絡

第二步、是在執行AbstractJdbc2Statement.executeWithFlags方法卡住的post

第三步、繼續跟蹤,並在網絡上查看可能引發的緣由是和設置fetchSize參數相關,因此我設置了fetchSize,奇葩的是沒有生效fetch

第四步、sendQuery,sendOneQuery方法,在這裏發現了問題,好在代碼不太多,我就都貼出來了:this

        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纔會生效。spa

boolean usePortal = (flags & 8) != 0 && !noResults && !noMeta && fetchSize > 0 && !describeOnly;

那麼我們逐一看一下這些條件:.net

  • !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
}

至此,問題終於被定位:

一、若是connection不是自動提交事務的,那麼,fetchSize將生效(非默認)

二、若是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

相關文章
相關標籤/搜索