最近作數據同步,須要從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
那麼,試下的惟一的可能致使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