java mysql大資料量批量插入與流式讀取分析
總結下這周幫助客戶解決報表生成操作的mysql 驅動的使用上的一些問題,與解決方案。由於生成報表邏輯要從資料庫讀取大量資料並在記憶體中加工處理後在
生成大量的彙總資料然後寫入到資料庫。基本流程是 讀取->處理->寫入。
1 讀取操作開始遇到的問題是當sql查詢資料量比較大時候基本讀不出來。開始以為是server端處理太慢。但是在控制檯是可以立即返回資料的。於是在應用
這邊抓包,發現也是傳送sql後立即有資料返回。但是執行ResultSet的next方法確實阻塞的。查文件翻程式碼原來mysql驅動預設的行為是需要把整個結果全部讀取到
記憶體中才開始允許應用讀取結果。顯然與期望的行為不一致,期望的行為是流的方式讀取,當結果從myql服務端返回後立即還是讀取處理。這樣應用就不需要大量記憶體
來儲存這個結果集。正確的流式讀取方式程式碼示例:
PreparedStatement ps = connection.prepareStatement("select .. from ..",
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
//forward only read only也是mysql 驅動的預設值,所以不指定也是可以的 比如: PreparedStatement ps = connection.prepareStatement("select .. from ..");
ps.setFetchSize(Integer.MIN_VALUE); //也可以修改jdbc url通過defaultFetchSize引數來設定,這樣預設所以的返回結果都是通過流方式讀取.
ResultSet rs = ps.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("fieldName"));
}
程式碼分析:下面是mysql判斷是否開啟流式讀取結果的方法,有三個條件forward-only,read-only,fatch size是Integer.MIN_VALUE
/**
* We only stream result sets when they are forward-only, read-only, and the
* fetch size has been set to Integer.MIN_VALUE
*
* @return true if this result set should be streamed row at-a-time, rather
* than read all at once.
*/
protected boolean createStreamingResultSet() {
try {
synchronized(checkClosed().getConnectionMutex()) {
return ((this.resultSetType == java.sql.ResultSet.TYPE_FORWARD_ONLY)
&& (this.resultSetConcurrency == java.sql.ResultSet.CONCUR_READ_ONLY) && (this.fetchSize == Integer.MIN_VALUE));
}
} catch (SQLException e) {
// we can't break the interface, having this be no-op in case of error is ok
return false;
}
}
2 批量寫入問題。開始時應用程式是一條一條的執行insert來寫入報表結果。寫入也是比較慢的。主要原因是單條寫入時候需要應用於db之間大量的
請求響應互動。每個請求都是一個獨立的事務提交。這樣網路延遲大的情況下多次請求會有大量的時間消耗的網路延遲上。第二個是由於每個事務db都會
有重新整理磁碟操作寫事務日誌,保證事務的永續性。由於每個事務只是寫入一條資料 所以磁碟io利用率不高,因為對於磁碟io是按塊來的,所以連續寫入大量資料效率
更好。所以必須改成批量插入的方式,減少請求數與事務數。下面是批量插入的例子:還有jdbc連線串必須加下rewriteBatchedStatements=true
int batchSize = 1000;
PreparedStatement ps = connection.prepareStatement("insert into tb1 (c1,c2,c3...) values (?,?,?...)");
for (int i = 0; i < list.size(); i++) {
ps.setXXX(list.get(i).getC1());
ps.setYYY(list.get(i).getC2());
ps.setZZZ(list.get(i).getC3());
ps.addBatch();
if ((i + 1) % batchSize == 0) {
ps.executeBatch();
}
}
if (list.size() % batchSize != 0) {
ps.executeBatch();
}
上面程式碼示例是每1000條資料傳送一次請求。mysql驅動內部在應用端會把多次addBatch()的引數合併成一條multi value的insert語句傳送給db去執行
比如insert into tb1(c1,c2,c3) values (v1,v2,v3),(v4,v5,v6),(v7,v8,v9)...
這樣可以比每條一個insert 明顯少很多請求。減少了網路延遲消耗時間與磁碟io時間,從而提高了tps。
。
程式碼分析: 從程式碼可以看出,
1 rewriteBatchedStatements=true,insert是引數化語句且不是insert ... select 或者 insert... on duplicate key update with an id=last_insert_id(...)的話會執行
executeBatchedInserts,也就是muti value的方式
2 rewriteBatchedStatements=true 語句是都是引數化(沒有addbatch(sql)方式加入的)的而且mysql server版本在4.1以上 語句超過三條,則執行executePreparedBatchAsMultiStatement
就是將多個語句通過;分隔一次提交多條sql。比如 "insert into tb1(c1,c2,c3) values (v1,v2,v3);insert into tb1(c1,c2,c3) values (v1,v2,v3)..."
3 其餘的執行executeBatchSerially,也就是還是一條條處理
public void addBatch(String sql)throws SQLException {
synchronized(checkClosed().getConnectionMutex()) {
this.batchHasPlainStatements = true;
super.addBatch(sql);
}
}
public int[] executeBatch()throws SQLException {
//...
if (!this.batchHasPlainStatements
&& this.connection.getRewriteBatchedStatements()) {
if (canRewriteAsMultiValueInsertAtSqlLevel()) {
return executeBatchedInserts(batchTimeout);
}
if (this.connection.versionMeetsMinimum(4, 1, 0)
&& !this.batchHasPlainStatements
&& this.batchedArgs != null
&& this.batchedArgs.size() > 3 /* cost of option setting rt-wise */
)
{
return executePreparedBatchAsMultiStatement(batchTimeout);
}
}
return executeBatchSerially(batchTimeout);
//.....
}
executeBatchedInserts相比executePreparedBatchAsMultiStatement的方式傳輸效率更好,因為一次請求只重複一次前面的insert table (c1,c2,c3)
mysql server 對請求報文的最大長度有限制,如果batch size 太大造成請求報文超過最大限制,mysql 驅動會內部按最大報文限制查分成多個報文。所以要真正減少提交次數
還要檢查下mysql server的max_allowed_packet 否則batch size 再大也沒用.
mysql> show VARIABLES like '%max_allowed_packet%';
+--------------------+-----------+
| Variable_name | Value |
+--------------------+-----------+
| max_allowed_packet | 167772160 |
+--------------------+-----------+
1 row in set (0.00 sec)
要想驗證mysql 傳送了正確的sql 有兩種方式
1 抓包,下圖是wireshark在 應用端抓包mysql的報文
2 另一個辦法是在mysql server端開啟general log 可以檢視mysql收到的所有sql
3 在jdbc url上加上引數traceProtocol=true 或者profileSQL=true or autoGenerateTestcaseScript=true
效能測試對比
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.alibaba.druid.pool.DruidDataSource;
public class BatchInsert {
public static void main(String[] args) throws SQLException {
int batchSize = 1000;
int insertCount = 1000;
testDefault(batchSize, insertCount);
testRewriteBatchedStatements(batchSize,insertCount);
}
private static void testDefault(int batchSize, int insertCount) throws SQLException {
long start = System.currentTimeMillis();
doBatchedInsert(batchSize, insertCount,"");
long end = System.currentTimeMillis();
System.out.println("default:" + (end -start) + "ms");
}
private static void testRewriteBatchedStatements(int batchSize, int insertCount) throws SQLException {
long start = System.currentTimeMillis();
doBatchedInsert(batchSize, insertCount, "rewriteBatchedStatements=true");
long end = System.currentTimeMillis();
System.out.println("rewriteBatchedStatements:" + (end -start) + "ms");
}
private static void doBatchedInsert(int batchSize, int insertCount, String mysqlProperties) throws SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://ip:3306/test?" + mysqlProperties);
dataSource.setUsername("name");
dataSource.setPassword("password");
dataSource.init();
Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("insert into Test (name,gmt_created,gmt_modified) values (?,now(),now())");
for (int i = 0; i < insertCount; i++) {
preparedStatement.setString(1, i+" ");
preparedStatement.addBatch();
if((i+1) % batchSize == 0) {
preparedStatement.executeBatch();
}
}
preparedStatement.executeBatch();
connection.close();
dataSource.close();
}
}
網路環境ping測試延遲是35ms ,測試結果:
default:75525ms
rewriteBatchedStatements:914ms
相關推薦
java mysql大資料量批量插入與流式讀取分析
總結下這周幫助客戶解決報表生成操作的mysql 驅動的使用上的一些問題,與解決方案。由於生成報表邏輯要從資料庫讀取大量資料並在記憶體中加工處理後在 生成大量的彙總資料然後寫入到資料庫。基本流程是 讀取->處理->寫入。 1 讀取操作開始遇到的問題是當sql查詢資料量比較大時候基本讀不出來。開始
java excel大資料量匯入匯出與優化
package com.hundsun.ta.utils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java
MySql 大資料量快速插入和語句優化
INSERT語句的速度 插入一個記錄需要的時間由下列因素組成,其中的數字表示大約比例:連線:(3) 傳送查詢給伺服器:(2) 分析查詢:(2) 插入記錄:(1x記錄大小) 插入索引:(1x索引) 關閉:(1) 這不考慮開啟表的初始開銷,每個併發執行的查詢開啟。
Mysql大資料量問題與解決
今日格言:瞭解了為什麼,問題就解決了一半。 Mysql 單表適合的最大資料量是多少? 我們說 Mysql 單表適合儲存的最大資料量,自然不是說能夠儲存的最大資料量,如果是說能夠儲存的最大量,那麼,如果你使用自增 ID,最大就可以儲存 2^32 或 2^64 條記錄了,這是按自增 ID 的資料型別 int
mysql大資料量下優化
1 優化sql和索引2 增加快取如:redis3 主從複製或主主複製,讀寫分離4 利用mysql自帶分割槽表5 先做垂直拆分,將一個大系統分為多個小系統,也就是分散式6 水平切分,要選擇一個合理的sharding key,為了有好的查詢效率,表結構也要改動,做一定的冗餘,應用也要改,sql中儘量帶shardi
java po大資料量Excel
POI之前的版本不支援大資料量處理,如果資料過多則經常報OOM錯誤,有時候調整JVM大小效果也不是太好。3.8版本的POI新出來了SXSSFWorkbook,可以支援大資料量的操作 /** * POI匯出工具類 */ publi
MySQL大資料量分頁查詢方法及其優化 MySQL大資料量分頁查詢方法及其優化
MySQL大資料量分頁查詢方法及其優化 ---方法1: 直接使用資料庫提供的SQL語句---語句樣式: MySQL中,可用如下方法: SELECT * FROM 表名稱 LIMIT M,N ---適應場景: 適用於資料量較少的情況(元組百/千級) --
java list大資料量用addAll
問題是這樣產生的,網上一哥們發了一個面試題: ListA 裡面有 1 2 3 ListB裡面有 4 5 6 讓Lis
Java POI大資料量的Excel匯入匯出
1. 大資料量的匯入 當Excel中的資料量超過10萬行時,在用POI讀取檔案流時很容易引起失敗,需要引入xlsx-streamer來進行資源的開啟,剩下的處理同POI處理上百行資料量類似:filePath=>FileInputStream=>Workboo
MySQL大資料量分頁查詢方法及其優化 ---方法1: 直接使用資料庫提供的SQL語句 ---語句樣式: MySQL中,可用如下方法: SELECT * FROM 表名稱 LIMIT M,N ---適
測試實驗 1. 直接用limit start, count分頁語句, 也是我程式中用的方法: select * from product limit start, count 當起始頁較小時,查詢沒有效能問題,我們分別看下從10, 100, 1000, 10000開始分頁的執行時間(每頁取20條), 如
MySQL 大資料量表優化方案
單表優化 除非單表資料未來會一直不斷上漲(例如網路爬蟲),否則不要一開始就考慮拆分,拆分會帶來邏輯、部署、運維的各種複雜度 一般以整型值為主的表在 千萬級以下,字串為主的表在 五百萬以下是沒有太大問題的。而事實上很多時候 MySQL 單表的效能依然有不少優化空間,甚至能正
mysql 大資料量分頁優化
假設有一個千萬量級的表,取1到10條資料; select * from table limit 0,10; select * from table limit 1000,10; 這兩條語句查詢時間應該在毫秒級完成; select * from table limit
MySQL大資料量分頁查詢方法及其優化
方法1: 直接使用資料庫提供的SQL語句 語句樣式: MySQL中,可用如下方法: SELECT * FROM 表名稱 LIMIT M,N 適應場景: 適用於資料量較少的情況(元組百/千級) 原因/缺點: 全表掃描,速度會很慢 且 有的資料庫結果集返回不穩定(如某次返回
通過索引,極大提高MySQL大資料量下的查詢效率
我在這裡測試了兩個表的左連線查詢,SQL語句是:select a.blog_id,a.blog_title,a.blog_thumb,a.blog_click,a.blog_addtime,a.blog_show,b.blog_category_name from `thin
java專案——大資料量的處理
1. 給定a、b兩個檔案,各存放50億個url,每個url各佔64位元組,記憶體限制是4G,讓你找出a、b檔案共同的url? 方案1:可以估計每個檔案安的大小為50G×64=320G,遠遠大於記憶體
mysql 大資料量時 limit查詢優化
一般,我們在做分頁時,用的是語句如下:select * from table LIMIT 5,10; #返回第6-15行資料但是,如果資料量很大,比如>1000萬,則利用以上的查詢會非常慢,可以利用以下語句進行優化:Select * From table Where I
(轉)SQLite之大資料量批量入庫
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; i
java專案——大資料量的處理 標籤: 大資料記憶體儲存
15. 最大間隙問題 給定n個實數 ,求著n個實數在實軸上向量2個數之間的最大差值,要求線性的時間演算法。 方案1:最先想到的方法就是先對這n個數據進行排序,然後一遍掃描即可確定相鄰的最大間隙。但該方法不能滿足線性時間的要求。故採取如下方法: s 找到n個數據中最大和最小資料max和min。 s 用n
MySQL大資料量快速分頁實現
以下分享一點我的經驗 一般剛開始學SQL語句的時候,會這樣寫 程式碼如下: SELECT * FROM table ORDER BY id LIMIT 1000, 10; 但在資料達到百萬級的時候,這樣寫會慢死 程式碼如下: SELECT * FROM tabl
Mysql大資料量儲存及訪問的設計討論-設計
轉載請註明來源:Mysql大資料量儲存及訪問的設計討論 一、引言 隨著網際網路應用的廣泛普及,海量資料的儲存和訪問成為了系統設計的瓶頸問題。對於一個大型的網際網路應用,每天幾十億的PV無疑對資料庫造成了相當高的負載。對於系統的穩定性和擴充套件性造成了極大的問題。通過