1. 程式人生 > >記一個 MySQL Streaming result set 時的小錯誤

記一個 MySQL Streaming result set 時的小錯誤

同事使用kettle遷移MySQL資料時出現了 SQLException,詳細報錯資訊如下:

Error reading from database: java.sql.SQLException: Streaming result set com.mysql.jdbc.RowDataDynamic@xxxxxxx is still active. No statements may be issued when any streaming result sets are open and in use on a given connection. Ensure that you have called .close() on any active streaming result sets before attempting more queries.

經查詢MySQL官方文件(MySQL版本 5.1-8.0 一樣),發現報錯原因是JDBC沒有處理完resultSet結果集時又使用同一個connection提交了新的query。下面是官方文件對ResultSet的JDBC實現說明:

預設情況下,ResultSet會一次性返回結果集並儲存在記憶體中。這也是最有效率且最容易實現的一種方式。但是當ResultSet含有大量資料(很多行、或者包含大物件的情況下)時,JVM可能無法分配ResultSet要求的記憶體,這時你可以讓driver每次返回(stream)一行資料。

要開啟這個功能,需要以如下的方式來建立Statement:

stmt = conn.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY,
              java.sql.ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);

注意上面的statement建立語句,使用了 TYPE_FORWARD_ONLY、CONCUR_READ_ONLY,並將fetchSize設定為 Integer.MIN_VALUE。這種設定告訴driver需要逐行處理 result set。

這種方式也有風險。在使用同一個 connection 來發起新的查詢之前,你必須:1)讀取 result set中的全部資料;2)或者將其關閉。否則就會丟擲我們前面所說的異常。

下面來說說事務。此 Statement 持有的鎖最快也要在statement 結束時才能釋放。這裡的鎖包括了 MyISAM的表級鎖或者InnoDB的行鎖。

當 Statement處在事務中時,鎖將在事務完成時釋放(事務結束了,當然此Statement也完成了)。像大多數其他資料庫一樣,Statement只能在其中的所有results都被讀取,或者相應的Result set被關閉之後才能關閉。

所以如果使用了streaming results 而又想併發訪問相應的表時,你就必須儘快來處理 statement 產生的result set。

MySQL提供了一個可選的方案,即基於遊標的 streaming。與逐行掃描不同的是它可以一次性讀取多行資料。要使用這種方式,你需要將 useCursorFetch 設定為true,然後呼叫 setFetchSize 方法來設定你希望一次獲取的行數:

conn = DriverManager.getConnection("jdbc:mysql://localhost/?useCursorFetch=true", "user", "s3cr3t");
stmt = conn.createStatement();
stmt.setFetchSize(100);
rs = stmt.executeQuery("SELECT * FROM your_table_here");

emmm~,感覺沒啥好補充的了。如果記憶體夠大就沒必要這麼麻煩,直