昨天在項目中需要對日志的查詢結果進行導出功能。
日志導出功能的實現是這樣的,輸入查詢條件,然后對查詢結果進行導出。由於日志數據量比較大。多的時候,有上億條記錄。
之前的解決方案都是多次查詢,然后使用limit 限制每次查詢的條數。然后導出。這樣的結果是效率比較低效。
那么能不能一次查詢就把所有結果倒出來了?於是我就使用一次查詢,不使用limit分頁。結果出現 java.lang.OutOfMemoryError: Java heap space問題。
看來是DB服務器端將一次將查詢到的結果集全部發送到Java端保存在內存中。由於結果集比較大,所以出現OOM問題。
首先我想到的是游標功能。那么是不是可以使用游標,一次從服務器端慢慢的取呢?上網查詢了一下,大家都說MySQL不支持游標功能等等。
后來就去看JDBC代碼。找到了setFetchSize()方法,結果設置以后,卻不能生效,還是出現OOM問題。
我的設置如下
ps=conn.con.prepareStatement("select * from bigTable"); ps.setFetchSize(1000);
后來老大在MySQL看到了這樣的方法:
ps = (PreparedStatement) con.prepareStatement("select * from bigTable", ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); ps.setFetchSize(Integer.MIN_VALUE); ps.setFetchDirection(ResultSet.FETCH_REVERSE);
設置以后,果然可以解決我的問題。
附上代碼:
package com.seven.dbTools.DBTools; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; public class JdbcHandleMySQLBigResultSet { public static long importData(String sql){ String url = "jdbc:mysql://ipaddress:3306/test?user=username&password=password"; try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } long allStart = System.currentTimeMillis(); long count =0; Connection con = null; PreparedStatement ps = null; Statement st = null; ResultSet rs = null; try { con = DriverManager.getConnection(url); ps = (PreparedStatement) con.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); ps.setFetchSize(Integer.MIN_VALUE); ps.setFetchDirection(ResultSet.FETCH_REVERSE); rs = ps.executeQuery(); while (rs.next()) { //此處處理業務邏輯 count++; if(count%600000==0){ System.out.println(" 寫入到第 "+(count/600000)+" 個文件中!"); long end = System.currentTimeMillis(); } } System.out.println("取回數據量為 "+count+" 行!"); } catch (SQLException e) { e.printStackTrace(); } finally { try { if(rs!=null){ rs.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if(ps!=null){ ps.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if(con!=null){ con.close(); } } catch (SQLException e) { e.printStackTrace(); } } return count; } public static void main(String[] args) throws InterruptedException { String sql = "select * from test.bigTable "; importData(sql); } }
最近對JDBC有了進一步的了解。關於JDBC,推薦我的另一篇文章,用於解決不寫文件,從Java IO流中直接導入數據到MySQL:
Java不寫文件,LOAD DATA LOCAL INFILE大批量導入數據到MySQL的實現 http://blog.csdn.net/chenyechao/article/details/9237495
推薦另外兩篇來自 阿里巴巴 葉正盛的文章我轉載的:
http://blog.csdn.net/chenyechao/article/details/9303979
這篇文章是我解決問題以后才看到的,上面已經說明了MySQL JDBC的setFetchSize的使用。