在向數(shù)據(jù)庫中添加數(shù)據(jù)時,難免會遇到批量添加數(shù)據(jù)的問題。下面就是使用JDBC來實現(xiàn)批量插入的幾種方法。
準備工作:
names
表id
:主鍵,自增name
:varchar(25),保證長度夠用就行CREATE TABLE names(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(25)
);
方法一:
最直接的頻繁執(zhí)行SQL語句來插入
long start = System.currentTimeMillis();
// 獲取數(shù)據(jù)庫連接
Connection conn= DriverManager.getConnection(url, user, password);
// SQL語句
String sql = "insert into names(name) values(?);";
// 預編譯SQL語句
PreparedStatement ps = conn.prepareStatement(sql);
// 批量插入 2萬 條數(shù)據(jù)
for (int i = 0; i < 20000; i++) {
ps.setObject(1, "name_"+i); // 填充占位符?
ps.execute(); // 每一條數(shù)據(jù)都執(zhí)行一次
}
long end = System.currentTimeMillis();
System.out.println("花費的時間為:" + (end - start)); // 花費的時間為:794551
// 關閉資源
ps.close();
conn.close();
方式二:
使用executeBatch()
來批量插入數(shù)據(jù)
需要在數(shù)據(jù)庫連接的url中添加rewriteBatchedStatements=true
字段,讓數(shù)據(jù)庫開啟批處理默認
long start = System.currentTimeMillis();
// 獲取數(shù)據(jù)庫連接
Connection conn= DriverManager.getConnection(url, user, password);
// SQL語句
String sql = "insert into names(name) values(?)"; // 注意這里! 一定不要加結尾的分號;
// 預編譯SQL語句
PreparedStatement ps = conn.prepareStatement(sql);
// 批量插入 100萬 條數(shù)據(jù)
for (int i = 1; i <= 1000000; i++) {
ps.setObject(1, "name_"+i);
// 添加到同一batch中
ps.addBatch();
if (i % 500 == 0) { // 每批次夠500條才執(zhí)行 控制這個數(shù)也可以提高點速度
// 執(zhí)行該batch的插入操作
ps.executeBatch();
// 清空已執(zhí)行的batch
ps.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("花費的時間為:" + (end - start)); // 花費的時間為:5177
// 關閉資源
ps.close();
conn.close();
注意:一定不要給SQL語句添加結尾的
;
。否則會拋異常。
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語句來批量添加數(shù)據(jù)時,可以這樣
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()
,都是將要放在占位符?
的數(shù)據(jù)先存在ArrayList
中,當執(zhí)行executeBatch()
時,遍歷ArrayList
將第一個數(shù)據(jù)"name_1"
放在SQL語句的?
處,后續(xù)的全部構造成,("name_2")
、,("name_3")
的形式連接在這條SQL語句后面,最終構造成一個長的插入SQL語句,再執(zhí)行,完成批量插入。
即:
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")
這樣由于執(zhí)行拼在一起的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語句的語法檢查或語義檢查無法通過,就會拋異常。
數(shù)據(jù)庫中也不會有"name_1"這條數(shù)據(jù)。
以上是筆者的推測,并沒有通過JDBC源碼驗證。
方式三:
在方式二的基礎上再進一步優(yōu)化,除了改變一批次的容量(上面是500)外,還可以設置不允許自動提交數(shù)據(jù),改為手動提交數(shù)據(jù)。
// 設置不允許自動提交數(shù)據(jù)
conn.setAutoCommit(false); // 該行代碼放在獲取數(shù)據(jù)庫連接后
// ... 批量插入操作同上
// 提交數(shù)據(jù)
conn.commit(); // 在批量插入的for循環(huán)后
// 花費時間為:3954
另外,還有個executeLargeBatch()
方法
當要總共要插入1億條數(shù)據(jù),并且一個batch為100萬
executeBatch()
花費了413635毫秒
executeLargeBatch()
花費了386389毫秒
emmm...可能不是單純替換著用的,哈哈哈!
聯(lián)系客服