第一週:JDBC中批量插入數據問題

在向數據庫中添加數據時,不免會遇到批量添加數據的問題。下面就是使用JDBC來實現批量插入的幾種方法。java

準備工做:mysql

  • 在MySQL5數據庫中建立一個names
  • 表中就兩個字段
    • id:主鍵,自增
    • name:varchar(25),保證長度夠用就行
CREATE TABLE names(
	id INT PRIMARY KEY AUTO_INCREMENT,
	name VARCHAR(25)
);

方法一:sql

最直接的頻繁執行SQL語句來插入數據庫

long start = System.currentTimeMillis();

// 獲取數據庫鏈接 
Connection conn= DriverManager.getConnection(url, user, password);
// SQL語句
String sql = "insert into names(name) values(?);";
// 預編譯SQL語句
PreparedStatement ps = conn.prepareStatement(sql);

// 批量插入 2萬 條數據
for (int i = 0; i < 20000; i++) {
    ps.setObject(1, "name_"+i); // 填充佔位符?
    ps.execute(); // 每一條數據都執行一次
}

long end = System.currentTimeMillis();

System.out.println("花費的時間爲:" + (end - start)); // 花費的時間爲:794551

// 關閉資源
ps.close();
conn.close();

方式二:優化

使用executeBatch()來批量插入數據url

須要在數據庫鏈接的url中添加rewriteBatchedStatements=true字段,讓數據庫開啓批處理默認code

long start = System.currentTimeMillis();

// 獲取數據庫鏈接 
Connection conn= DriverManager.getConnection(url, user, password);
// SQL語句
String sql = "insert into names(name) values(?)"; // 注意這裏! 必定不要加結尾的分號;
// 預編譯SQL語句
PreparedStatement ps = conn.prepareStatement(sql);

// 批量插入 100萬 條數據
for (int i = 1; i <= 1000000; i++) {
    ps.setObject(1, "name_"+i);

    // 添加到同一batch中
    ps.addBatch();

    if (i % 500 == 0) { // 每批次夠500條才執行 控制這個數也能夠提升點速度
        // 執行該batch的插入操做
        ps.executeBatch();

        // 清空已執行的batch
        ps.clearBatch();
    }
}

long end = System.currentTimeMillis();

System.out.println("花費的時間爲:" + (end - start)); // 花費的時間爲:5177

// 關閉資源
ps.close();
conn.close();

注意:必定不要給SQL語句添加結尾的;。不然會拋異常。server

java.sql.BatchUpdateException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '('name_2'),('name_3'),('name_4'),('name_5'),('name_6'),('name_7'),('name_8'),('n' at line 1...

至於緣由,JDBC的源碼實在太晦澀了,看一會就先後鏈接不上了,因此筆者認爲應該是因爲在MySQL中批量插入的SQL語句的問題。資源

就是對於names表,直接在MySQL中用SQL語句來批量添加數據時,能夠這樣get

insert into names(`name`) values("name_1"), ("name_2"), ("name_3");

這行去掉;也能正常運行

可是若是這樣,注意分號

insert into names(`name`) values("name_1");, ("name_2"), ("name_3")

那麼"name_1"插入表中,後面2和3沒有,而且MySQL拋異常。

​ 那麼或許在JDBC中,每次addBatch(),都是將要放在佔位符?的數據先存在ArrayList中,當執行executeBatch()時,遍歷ArrayList將第一個數據"name_1"放在SQL語句的?處,後續的所有構形成,("name_2"),("name_3")的形式鏈接在這條SQL語句後面,最終構形成一個長的插入SQL語句,再執行,完成批量插入。

即:

insert into names(`name`) values("name_1")
insert into names(`name`) values("name_1"), ("name_2")
insert into names(`name`) values("name_1"), ("name_2"), ("name_3")
insert into names(`name`) values("name_1"), ("name_2"), ("name_3")..., ("name_batchSize")

這樣因爲執行拼在一塊兒的SQL就能夠完成批量插入。

可是若是insert into names(name) values(?);結尾有個;,就變成這樣:

insert into names(`name`) values("name_1");
insert into names(`name`) values("name_1");, ("name_2")
insert into names(`name`) values("name_1");, ("name_2"), ("name_3")
insert into names(`name`) values("name_1");, ("name_2"), ("name_3")..., ("name_batchSize")

那麼JDBC的對SQL語句的語法檢查語義檢查沒法經過,就會拋異常。

數據庫中也不會有"name_1"這條數據。

以上是筆者的推測,並無經過JDBC源碼驗證。

方式三:

在方式二的基礎上再進一步優化,除了改變一批次的容量(上面是500)外,還能夠設置不容許自動提交數據,改成手動提交數據。

// 設置不容許自動提交數據
conn.setAutoCommit(false); // 該行代碼放在獲取數據庫鏈接後

// ... 批量插入操做同上

// 提交數據
conn.commit(); // 在批量插入的for循環後

// 花費時間爲:3954

另外,還有個executeLargeBatch()方法

當要總共要插入1億條數據,而且一個batch爲100萬

executeBatch()花費了413635毫秒

executeLargeBatch()花費了386389毫秒

emmm...可能不是單純替換着用的,哈哈哈!

相關文章
相關標籤/搜索