正確使用MySQL JDBC setFetchSize

來源:互聯網
上載者:User

標籤:tar   dex   開始   add   values   cti   時間   慢慢   dir   

MYSQL JDBC快速查詢響應的方法,快速返回機制的實現

一直很糾結,Oracle的快速返回機制,雖然結果集很多,可是它能很快的顯示第一個結果,雖然通過MYSQl的用戶端可以做到,但是通過JDBC卻不行。

今天用了1個多小時,終於搞定此問題,希望對廣大Java朋友在處理資料庫時有個參考。

 

來由:

    通過命令列用戶端加上-q參數,可以極快的響應一個查詢。
    比如結果集為幾千萬的select * from t1,完整結果集需要20秒,通過-q參數顯示第一行只需要不到1秒。
    但通過jdbc進行查詢,卻不可以實現如上的效果,無論怎麼調整URL參數,也不行。
    
過程:
    查看了-q參數的解釋,如下:
    If you have problems due to insufficient memory for large result sets, 
    use the --quick option. This forces mysql to retrieve results 
    from the server a row at a time rather than retrieving the entire result set 
    and buffering it in memory before displaying it. This is done by returning 
    the result set using the mysql_use_result() C API function in the client/server 
    library rather than mysql_store_result().
    
    可見,實現快速響應。
    
    查看 mysql_use_result() 函數,這個是C的API,如果通過C開發,可以用這個函數。
    
    那麼JAVA呢?
    
    尋找標準JDBC規範裡面有關函數,沒有任何收穫。 setFetchSize()看上去有效,可在實際測試裡,無任何效能提升。
    
    搜尋 JDBC mysql_use_result, 有了意外的收穫。
    
    在MYSQL的JDBC,com.mysql.jdbc.Statement 這個介面裡發現了如下的內容:
     abstract public  void disableStreamingResults() throws SQLException

    Resets this statements fetch size and result set type to the values they 
    had before enableStreamingResults() was called.

 abstract public  void enableStreamingResults() throws SQLException

    Workaround for containers that ‘check‘ for sane values of Statement.setFetchSize() 
    so that applications can use the Java variant of libmysql‘s mysql_use_result() behavior. 
    
    
  原來MySQL提供了自己的一個快速響應的實現。調整測試代碼
  
      stmt = (com.mysql.jdbc.Statement) con.createStatement();
      stmt.setFetchSize(1);
        //按行讀取
        // 開啟流方式返回機制
        stmt.enableStreamingResults();
        
        我期待的效果出現了。第一行資料被快速的現實出來,時間不到1秒中。
        
結論:
    MySQL在自己的JDBC驅動裡提供了特有的功能,來實現查詢的快速響應,

    特別是結果集非常大或者時間較長,而使用者非常想儘快看到第一條結果時特別有效。

from:http://blog.csdn.net/java2000_net/article/details/6869752

 

正確使用MySQL JDBC setFetchSize()方法解決JDBC處理大結果集 java.lang.OutOfMemoryError: Java heap space

昨天在項目中需要對日誌的查詢結果進行匯出功能。

 

日誌匯出功能的實現是這樣的,輸入查詢條件,然後對查詢結果進行匯出。由於日誌資料量比較大。多的時候,有上億條記錄。

 

之前的解決方案都是多次查詢,然後使用limit 限制每次查詢的條數。然後匯出。這樣的結果是效率比較低效。

 

那麼能不能一次查詢就把所有結果倒出來了?於是我就使用一次查詢,不使用limit分頁。結果出現 java.lang.OutOfMemoryError: Java heap space問題。

 

看來是DB伺服器端將一次將查詢到的結果集全部發送到Java端儲存在記憶體中。由於結果集比較大,所以出現OOM問題。

 

首先我想到的是遊標功能。那麼是不是可以使用遊標,一次從伺服器端慢慢的取呢?上網查詢了一下,大家都說MySQL不支援遊標功能等等。

 

後來就去看JDBC代碼。找到了setFetchSize()方法,結果設定以後,卻不能生效,還是出現OOM問題。

我的設定如下

 

[java] view plaincopy 
  1. ps=conn.con.prepareStatement("select * from bigTable");  
  2.  ps.setFetchSize(1000);  



 

 

後來老大在MySQL看到了這樣的方法:

 

[java] view plaincopy 
  1. ps = (PreparedStatement) con.prepareStatement("select * from bigTable",  
  2.                 ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);  
  3.         ps.setFetchSize(Integer.MIN_VALUE);  
  4.         ps.setFetchDirection(ResultSet.FETCH_REVERSE);  

 

 

對此解釋是:MySQL JDBC預設用戶端資料接收方式為如下:

 

預設為從伺服器一次取出所有資料放在用戶端記憶體中,fetch size參數不起作用,當一條SQL返回資料量較大時可能會出現JVM OOM。

要一條SQL從伺服器讀取大量資料,不發生JVM OOM,可以採用以下方法之一:

 

1、當statement設定以下屬性時,採用的是流資料接收方式,每次只從伺服器接收部份資料,直到所有資料處理完畢,不會發生JVM OOM。

          setResultSetType(ResultSet.TYPE_FORWARD_ONLY);

          setFetchSize(Integer.MIN_VALUE); 

 

2、調用statement的enableStreamingResults方法,實際上enableStreamingResults方法內部封裝的就是第1種方式。

3、設定串連屬性useCursorFetch=true (5.0版驅動開始支援),statement以TYPE_FORWARD_ONLY開啟,再設定fetch size參數,表示採用伺服器端遊標,每次從伺服器取fetch_size條資料。

 

設定以後,果然可以解決我的問題。

 

附上代碼:

 

[java] view plaincopy 
  1. package com.seven.dbTools.DBTools;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.DriverManager;  
  5. import java.sql.PreparedStatement;  
  6. import java.sql.ResultSet;  
  7. import java.sql.SQLException;  
  8. import java.sql.Statement;  
  9. import java.util.ArrayList;  
  10.   
  11. public class JdbcHandleMySQLBigResultSet {  
  12.   
  13.     public static long importData(String sql){  
  14.         String url = "jdbc:mysql://ipaddress:3306/test?user=username&password=password";  
  15.         try {  
  16.             Class.forName("com.mysql.jdbc.Driver");  
  17.         } catch (ClassNotFoundException e1) {  
  18.             e1.printStackTrace();  
  19.         }  
  20.         long allStart = System.currentTimeMillis();  
  21.         long count =0;  
  22.   
  23.         Connection con = null;  
  24.         PreparedStatement ps = null;  
  25.         Statement st = null;  
  26.         ResultSet rs = null;  
  27.         try {  
  28.             con = DriverManager.getConnection(url);  
  29.               
  30.             ps = (PreparedStatement) con.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY,  
  31.                       ResultSet.CONCUR_READ_ONLY);  
  32.                         
  33.             ps.setFetchSize(Integer.MIN_VALUE);  
  34.               
  35.             ps.setFetchDirection(ResultSet.FETCH_REVERSE);  
  36.   
  37.             rs = ps.executeQuery();  
  38.   
  39.   
  40.             while (rs.next()) {  
  41.                   
  42.                 //此處處理商務邏輯  
  43.                 count++;  
  44.                 if(count%600000==0){  
  45.                     System.out.println(" 寫入到第  "+(count/600000)+" 個檔案中!");  
  46.                     long end = System.currentTimeMillis();  
  47.                 }  
  48.                   
  49.             }  
  50.             System.out.println("取回資料量為  "+count+" 行!");  
  51.         } catch (SQLException e) {  
  52.             e.printStackTrace();  
  53.         } finally {  
  54.             try {  
  55.                 if(rs!=null){  
  56.                     rs.close();  
  57.                 }  
  58.             } catch (SQLException e) {  
  59.                 e.printStackTrace();  
  60.             }  
  61.             try {  
  62.                 if(ps!=null){  
  63.                     ps.close();  
  64.                 }  
  65.             } catch (SQLException e) {  
  66.                 e.printStackTrace();  
  67.             }  
  68.             try {  
  69.                 if(con!=null){  
  70.                     con.close();  
  71.                 }  
  72.             } catch (SQLException e) {  
  73.                 e.printStackTrace();  
  74.             }  
  75.         }  
  76.         return count;  
  77.   
  78.     }  
  79.   
  80.     public static void main(String[] args) throws InterruptedException {  
  81.   
  82.         String sql = "select * from test.bigTable ";  
  83.         importData(sql);  
  84.   
  85.     }  
  86.   
  87. }  

正確使用MySQL JDBC setFetchSize

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.