1. 程式人生 > >jdbc mysql設定rewriteBatchedStatements引數實現高效能批量處理 executeBatch返回值問題

jdbc mysql設定rewriteBatchedStatements引數實現高效能批量處理 executeBatch返回值問題

一、摘要

利用jdbc預處理PreparedStatement.executeBatch可實現sql批處理,但是資料庫層面是否真正實現批處理,不同資料庫表現不一。以mysql為例,只有jdbcUrl設定了rewriteBatchedStatements=true引數,mysql驅動才會真正執行sql批處理,從而顯著提高效能。但是一旦設定rewriteBatchedStatements=true後,PreparedStatement.executeBatch()的返回結果也會發生變化,為此程式碼需要特殊處理。

二、有無引數rewriteBatchedStatements比較

1、資料表 jdbc_student

CREATE TABLE `jdbc_student` (
  `id` int(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

2、沒有rewriteBatchedStatements=true引數

java程式碼

public static void main(String[] args)
{ Connection conn = null; try { Class.forName("com.mysql.jdbc.Driver").newInstance(); String jdbcUrl = "jdbc:mysql://ip:3306/dbname?useUnicode=true&characterEncoding=utf-8"; conn = DriverManager.getConnection(jdbcUrl,"user", "pwd"); int batchSize = 5000; int count = 0; conn.setAutoCommit(
false); //設定自動提交為false PreparedStatement ps = conn.prepareStatement("insert into jdbc_student (name, age) values (?,?)"); for (int i = 1; i <= batchSize; i++) { ps.setString(1, "name: " + i); //設定第2個引數, name ps.setInt(2, i % 30 + 10); //設定第3個引數, age ps.addBatch(); //將該條記錄新增到批處理中 } Long t1 = System.currentTimeMillis(); System.out.println("開始執行: " + t1); int rows[] = ps.executeBatch(); for(int row : rows) { count += row; } conn.commit(); //提交 ps.close(); Long t2 = System.currentTimeMillis(); System.out.println("執行結束:" + t2 + ", 耗時:"+(t2-t1)/1000+"秒, batchRows: "+batchSize+"條, affectedRows: " + count); if(conn!=null) { conn.close(); } } catch (Exception e) { e.printStackTrace(); if(conn!=null) { try { conn.close(); } catch (Exception ee) { //TODO } } } }

輸出結果

開始執行: 1540629124028
執行結束: 1540629189490, 耗時:65秒, batchRows: 5000條, affectedRows: 5000

3、設定rewriteBatchedStatements=true引數

java程式碼

public static void main(String[] args) {
	Connection conn = null;
	try {
		Class.forName("com.mysql.jdbc.Driver").newInstance();
		String jdbcUrl = "jdbc:mysql://ip:3306/dbname?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf-8";
		conn = DriverManager.getConnection(jdbcUrl,"user", "pwd");
		int batchSize = 5000;
		int count = 0;
		conn.setAutoCommit(false);	//設定自動提交為false
		PreparedStatement ps = conn.prepareStatement("insert into jdbc_student (name, age) values (?,?)");
		for (int i = 1; i <= batchSize; i++) {
		    ps.setString(1, "name: " + i);	//設定第2個引數, name
		    ps.setInt(2, i % 30 + 10);		//設定第3個引數, age
		    ps.addBatch();		//將該條記錄新增到批處理中
		}
		Long t1 = System.currentTimeMillis();
		System.out.println("開始執行: " + t1);
        int rows[] = ps.executeBatch();
        for(int row : rows) {
        	count += row;
        }
        conn.commit();	//提交
		ps.close();
		Long t2 = System.currentTimeMillis();
		System.out.println("執行結束:" + t2 + ", 耗時:"+(t2-t1)/1000+"秒, batchRows: "+batchSize+"條, affectedRows: " + count);
		if(conn!=null) {
			conn.close();
		}
	} catch (Exception e) {
		e.printStackTrace();
		if(conn!=null) {
			try {
				conn.close();
			} catch (Exception ee) {
				//TODO
			}
		}
	}
}

輸出結果

開始執行: 1540629414786
執行結束: 1540629414931, 耗時:0秒, batchRows: 5000條, affectedRows: 25000000

三、executeBatch返回值處理

1、有無rewriteBatchedStatements引數,兩者執行效能真是天壤之別:

無rewriteBatchedStatements引數,執行時間:65秒
有rewriteBatchedStatements引數,執行時間:不到1秒

2、executeBatch返回值:

無rewriteBatchedStatements引數,返回值:int[]累加,5000
有rewriteBatchedStatements引數,返回值:int[]累加,25000000

3、有rewriteBatchedStatements引數,executeBatch返回值解釋:

1)設定rewriteBatchedStatements引數後,可以簡單理解為資料庫將5000條insert-sql語句打包成一條sql語句,執行這條sql語句返回總影響行數5000, 如:
INSERT INTO jdbc_student VALUES (‘55000’, ‘name: 1’, ‘11’), (‘55001’, ‘name: 1’, ‘11’), (‘55002’, ‘name: 2’, ‘12’),;
2)但是驅動executeBatch返回的是每條sql語句對應的影響記錄數陣列,即[5000,5000,5000,…,5000],所以累計就是5000*5000 = 25000000
3)update, delete, 返回的仍然是[1,1,1…1]無需特殊處理。

4、executeBatch返回值應用

以銷售模組更新訂單主細表為例,假設訂單細表有10條行專案,分2張出庫單出庫,分別有不同人員操作(誰先操作不確定):
1)第一張出庫單出庫4個訂單行專案,出庫後
訂單細表行專案狀態:已排程->已出庫
訂單主表狀態:已排程->(行專案是否全部出庫 ? ‘已出庫’ : ‘部分出庫’)
2)第二張出庫單出庫6個訂單行專案,出庫後
訂單細表行專案狀態:已排程->已出庫
訂單主表狀態:已排程->(行專案是否全部出庫 ? ‘已出庫’ : ‘部分出庫’)

sql批處理的封裝,以java程式碼為例,假設呼叫sql批處理程式碼封裝如下:

public int executeBatch(String sql, List<Map> paras) {
	//預處理sql:insert into jdbc_student (name, age) values (?,?)
	//paras,引數集合,[{name:"name1",age:11},{name:"name2",age:12},...]
	//返回影響記錄條數
}

更新訂單主細表狀態程式碼邏輯:

  • 開啟事務
  • 更新訂單細表(狀態, 操作時間, 操作人, 出庫數量等): 已排程->已出庫,jdbc批處理實現,判斷executeBatch(sql, paras) == paras.size()
  • 查詢細表是否全部已出庫?並更新主表狀態
  • 提交事務

以出庫單1為例:

  • 沒rewriteBatchedStatements引數,executeBatch(sql, paras) == paras.size() == 4
  • 有rewriteBatchedStatements引數,executeBatch(sql, paras) = 16,paras.size() = 4,兩者不等,程式報錯!

實際業務中,通常paras.size即executeBatch返回結果,因此判斷是否相等修改如下即可:

executeBatch(sql, paras) == paras.size() || executeBatch(sql, paras) == paras.size() * paras.size()