第一周: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...可能不是单纯替换着用的,哈哈哈!

相关文章
相关标签/搜索