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


前言:

最近做數據同步,需要從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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM