使用JDBC进行批处理INSERTS的有效方法


71

在我的应用程序中,我需要执行很多插入操作。它是一个Java应用程序,我正在使用普通的JDBC执行查询。该数据库是Oracle。我已经启用了批处理,所以它节省了我执行查询所需要的网络延迟。但是查询作为独立的INSERT顺序执行:

insert into some_table (col1, col2) values (val1, val2)
insert into some_table (col1, col2) values (val3, val4)
insert into some_table (col1, col2) values (val5, val6)

我想知道以下形式的INSERT是否会更有效:

insert into some_table (col1, col2) values (val1, val2), (val3, val4), (val5, val6)

即将多个INSERT折叠成一个。

还有其他使批处理INSERT更快的技巧吗?


2
哇!在插入SQL Server时,我测试了“将多个插入合在一起”,我从每秒107行增加到每秒3333行!
Wouter

1
增长了惊人的31倍。
Gaurav

Answers:


148

这是前两个答案的混合:

  PreparedStatement ps = c.prepareStatement("INSERT INTO employees VALUES (?, ?)");

  ps.setString(1, "John");
  ps.setString(2,"Doe");
  ps.addBatch();

  ps.clearParameters();
  ps.setString(1, "Dave");
  ps.setString(2,"Smith");
  ps.addBatch();

  ps.clearParameters();
  int[] results = ps.executeBatch();

3
这是完美的解决方案,因为仅准备(解析)一次语句。
Ashish Patil

42
ps.clearParameters();是在该特定情况下是不必要的。
BalusC

1
确保对其进行测量。根据JDBC驱动程序的实现,这可能是预期的每批处理一次往返,但最终也可能是每条语句一次往返。
stracktracer 2014年

prepareStatement / setXXX-应该就是这样!
msciwoj

3
对于mysql,还将以下内容添加到url:“&useServerPrepStmts = false&rewriteBatchedStatements = true”
Ant Kutschera,

38

尽管该问题要求使用JDBC高效地插入Oracle,但我目前正在使用DB2(在IBM大型机上),但是从概念上讲,插入将是类似的,因此认为查看我之间的度量标准可能会有所帮助

  • 一次插入一条记录

  • 插入一批记录(非常有效)

衡量指标

1)一次插入一条记录

public void writeWithCompileQuery(int records) {
    PreparedStatement statement;

    try {
        Connection connection = getDatabaseConnection();
        connection.setAutoCommit(true);

        String compiledQuery = "INSERT INTO TESTDB.EMPLOYEE(EMPNO, EMPNM, DEPT, RANK, USERNAME)" +
                " VALUES" + "(?, ?, ?, ?, ?)";
        statement = connection.prepareStatement(compiledQuery);

        long start = System.currentTimeMillis();

        for(int index = 1; index < records; index++) {
            statement.setInt(1, index);
            statement.setString(2, "emp number-"+index);
            statement.setInt(3, index);
            statement.setInt(4, index);
            statement.setString(5, "username");

            long startInternal = System.currentTimeMillis();
            statement.executeUpdate();
            System.out.println("each transaction time taken = " + (System.currentTimeMillis() - startInternal) + " ms");
        }

        long end = System.currentTimeMillis();
        System.out.println("total time taken = " + (end - start) + " ms");
        System.out.println("avg total time taken = " + (end - start)/ records + " ms");

        statement.close();
        connection.close();

    } catch (SQLException ex) {
        System.err.println("SQLException information");
        while (ex != null) {
            System.err.println("Error msg: " + ex.getMessage());
            ex = ex.getNextException();
        }
    }
}

100笔交易的指标:

each transaction time taken = 123 ms
each transaction time taken = 53 ms
each transaction time taken = 48 ms
each transaction time taken = 48 ms
each transaction time taken = 49 ms
each transaction time taken = 49 ms
...
..
.
each transaction time taken = 49 ms
each transaction time taken = 49 ms
total time taken = 4935 ms
avg total time taken = 49 ms

第一个事务在处理120-150ms,用于查询解析然后执行,后续事务仅在处理50ms。(仍然很高,但是我的数据库在其他服务器上(我需要对网络进行故障排除))

2)批量插入(高效插入) -通过preparedStatement.executeBatch()

public int[] writeInABatchWithCompiledQuery(int records) {
    PreparedStatement preparedStatement;

    try {
        Connection connection = getDatabaseConnection();
        connection.setAutoCommit(true);

        String compiledQuery = "INSERT INTO TESTDB.EMPLOYEE(EMPNO, EMPNM, DEPT, RANK, USERNAME)" +
                " VALUES" + "(?, ?, ?, ?, ?)";
        preparedStatement = connection.prepareStatement(compiledQuery);

        for(int index = 1; index <= records; index++) {
            preparedStatement.setInt(1, index);
            preparedStatement.setString(2, "empo number-"+index);
            preparedStatement.setInt(3, index+100);
            preparedStatement.setInt(4, index+200);
            preparedStatement.setString(5, "usernames");
            preparedStatement.addBatch();
        }

        long start = System.currentTimeMillis();
        int[] inserted = preparedStatement.executeBatch();
        long end = System.currentTimeMillis();

        System.out.println("total time taken to insert the batch = " + (end - start) + " ms");
        System.out.println("total time taken = " + (end - start)/records + " s");

        preparedStatement.close();
        connection.close();

        return inserted;

    } catch (SQLException ex) {
        System.err.println("SQLException information");
        while (ex != null) {
            System.err.println("Error msg: " + ex.getMessage());
            ex = ex.getNextException();
        }
        throw new RuntimeException("Error");
    }
}

一批100笔交易的指标是

total time taken to insert the batch = 127 ms

和1000笔交易

total time taken to insert the batch = 341 ms

因此,进行了100次交易 ~5000ms(一次传送一个trxn)减少为~150ms(一批100条记录)。

注意-忽略我的超级慢网络,但指标值是相对的。


1
你好 记录的长度在插入时间中是否起作用?我有3个带有URI作为其值的Varchar列,并且作为批处理插入了8555,插入仍然需要大约3.5分钟!
Prathamesh dhanawade

根据我的理解,在将数据从应用程序服务器传输到数据库服务器的过程中,记录大小可能很重要,但是插入时间影响不大。我在本地Oracle数据库中尝试了3列大小为125字节的列,并为批处理10,000条记录花费了大约(145到300)ms。在这里编码。虽然为10000条记录多次交易花费20秒
祈祷

9

Statement为您提供了以下选项:

Statement stmt = con.createStatement();

stmt.addBatch("INSERT INTO employees VALUES (1000, 'Joe Jones')");
stmt.addBatch("INSERT INTO departments VALUES (260, 'Shoe')");
stmt.addBatch("INSERT INTO emp_dept VALUES (1000, 260)");

// submit a batch of update commands for execution
int[] updateCounts = stmt.executeBatch();

7
尽管最终结果是相同的,但是在此方法中,将解析多个语句,这对于批量处理而言要慢得多,实际上比单独执行每个语句效率不高。也请使用PreparedStatement的尽可能的重复查询,因为他们进行更好..
阿希什·帕蒂尔

@AshishPatil:有和没有PreparedStatement的测试是否有基准?
加拉夫

哇!8年后。但是,@ prayagupd在他的回答中给出了详细的统计数据,而这是最近的。stackoverflow.com/a/42756134/372055
Ashish Patil,

非常感谢你做的这些。当动态插入数据并且您没有时间检查参数的数据类型时,这确实很有用。
Morfinismo

5

显然,您必须进行基准测试,但是如果您使用PreparedStatement而不是Statement,那么通过JDBC发出多个插入将更快。



0

如何使用INSERT ALL语句?

INSERT ALL

INTO table_name VALUES ()

INTO table_name VALUES ()

...

SELECT Statement;

我记得最后一个select语句是强制执行的,才能使此请求成功。虽然不记得为什么。您可能会考虑使用PreparedStatement。优势很多!

法里德



0

在我的代码中,我无法直接访问“ preparedStatement”,因此无法使用批处理,我只是将查询和参数列表传递给了它。但是,技巧是创建可变长度的插入语句和参数的LinkedList。参数输入长度可变,其效果与最上面的示例相同(请参见下文(省略错误检查))。假设“ myTable”具有3个可更新字段:f1,f2和f3

String []args={"A","B","C", "X","Y","Z" }; // etc, input list of triplets
final String QUERY="INSERT INTO [myTable] (f1,f2,f3) values ";
LinkedList params=new LinkedList();
String comma="";
StringBuilder q=QUERY;
for(int nl=0; nl< args.length; nl+=3 ) { // args is a list of triplets values
    params.add(args[nl]);
    params.add(args[nl+1]);
    params.add(args[nl+2]);
    q.append(comma+"(?,?,?)");
    comma=",";
}      
int nr=insertIntoDB(q, params);

在我的DBInterface类中,我有:

int insertIntoDB(String query, LinkedList <String>params) {
    preparedUPDStmt = connectionSQL.prepareStatement(query);
    int n=1;
    for(String x:params) {
        preparedUPDStmt.setString(n++, x);
    }
    int updates=preparedUPDStmt.executeUpdate();
    return updates;
}

-5

如果您的迭代次数较少,则使用PreparedStatement的速度将比Statements慢得多。为了通过在语句上使用PrepareStatement获得性能优势,您需要在迭代次数至少为50或更高的循环中使用它。


6
不,永远不会。普通的Statement(不是PrepareStatement)对象必须执行PreparedStatement的所有相同操作,并且实际上是对PreparedStatement的包装,该包装器实际上也做准备好的部分。两者之间的区别在于,Statement对象以无提示的方式准备该语句,并在每次执行该语句时对其进行验证,而作为准备好的语句,该对象仅执行一次,然后可以多次执行以处理批处理中的每个项目。
大卫

这个答案完全有效吗?
祈祷之星

-13

使用语句批量插入

int a= 100;
            try {
                        for (int i = 0; i < 10; i++) {
                            String insert = "insert into usermaster"
                                    + "("
                                    + "userid"
                                    + ")"
                                    + "values("
                                    + "'" + a + "'"
                                    + ");";
                            statement.addBatch(insert);
                            System.out.println(insert);
                            a++;
                        }
                      dbConnection.commit();
                    } catch (SQLException e) {
                        System.out.println(" Insert Failed");
                        System.out.println(e.getMessage());
                    } finally {
            
                        if (statement != null) {
                            statement.close();
                        }
                        if (dbConnection != null) {
                            dbConnection.close();
                        }
                    }
        

动态语句几乎总是一个坏主意。为了安全(尽管在这个非常简单的示例中不是这种情况)和性能。
frroland
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.