JDBC批处理插入到Oracle中不起作用

时间:2018-07-30 08:11:53

标签: oracle jdbc batching

我正在使用JDBC的批处理来插入一百万行。我遇到了Oracle驱动程序无法按预期工作的情况-批量插入需要很长时间才能工作。 我决定通过Wireshark嗅探应用程序的流量。我看到了什么?

  • Oracle JDBC驱动程序发送了第一个请求(1)
  • 然后它发送数据(2),大约 2500行
  • oracle服务器用一些软件包响应(3)
  • 现在所有剩余数据将以一对一的插入方式发送,不批量!
    • insert into my_table...
    • insert into my_table...

为什么会这样?我该如何解决?

create table my_table (val number);

代码

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class scratch_1 {

    @Test
    public void foo() throws SQLException {
        String sql = "insert into my_table (val) values (?)";

        try (Connection con = getConnection()) {
            con.setAutoCommit(false);
            try (PreparedStatement ps = con.prepareStatement(sql)) {

                for (long i = 0; i < 100_000; i++) {
                    ps.setBigDecimal(1, BigDecimal.valueOf(i));

                    ps.addBatch();
                }

                ps.executeBatch();
                ps.clearBatch();
            }
            con.commit();
        }
    }

    private Connection getConnection() throws SQLException {
        String url = "jdbc:oracle:thin:@localhost:1521:orcl";
        String user = "my_user";
        String password = "my_password";
        return java.sql.DriverManager.getConnection(url, user, password);
    }
}

Wireshark代码说明发生了什么:

Wireshark log

环境

$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

Oracle Database 12.2.0.1 JDBC Driver

服务器:Oracle Database 11g企业版11.2.0.4.0-64位

多次运行查询无济于事-结果相同。 在250k中插入了465s行“批量”

在服务器端v$sql

SELECT *
FROM
  (SELECT REGEXP_SUBSTR (sql_text, 'insert into [^\(]*') sql_text,
    sql_id,
    TRUNC(
    CASE
      WHEN SUM (executions) > 0
      THEN SUM (rows_processed) / SUM (executions)
    END,2) rows_per_execution
  FROM v$sql
  WHERE parsing_schema_name = 'MY_SCHEMA'
  AND sql_text LIKE 'insert into%'
  GROUP BY sql_text,
    sql_id
  )
ORDER BY rows_per_execution ASC;

enter image description here

2 个答案:

答案 0 :(得分:2)

问题解决了

感谢您的所有回复。我非常感谢您!

我之前的示例未描述实际问题。抱歉,无法立即显示全部图片。
我将其简化为无法处理空值的状态。
请检查上面的示例,我已经对其进行了更新。
如果我使用java.sql.Types.NULL的Oracle JDBC驱动程序将theVarcharNullBinder用作null的值,那么它会导致某种奇怪的工作。我认为Driver用于批处理,直到未指定类型的第一个null为止,为null后将回退到一对一的插入。

java.sql.Types.NUMERIC列驱动程序使用的number更改为theVarnumNullBinder后,并正确使用它-完全批处理。

代码

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class scratch_1 {

    @Test
    public void foo() throws SQLException {
        String sql = "insert into my_table (val) values (?)";

        try (Connection con = getConnection()) {
            con.setAutoCommit(false);
            try (PreparedStatement ps = con.prepareStatement(sql)) {

                for (long i = 0; i < 100_000; i++) {
                    if (i % 2 == 0) {
                        //the real problem was here:
                        //ps.setNull(1, Types.NULL); //wrong way!
                        ps.setNull(1, Types.NUMERIC); //correct
                    } else {
                        ps.setBigDecimal(1, BigDecimal.valueOf(i));
                    }

                    ps.addBatch();
                }

                ps.executeBatch();
                ps.clearBatch();
            }
            con.commit();
        }
    }

    private Connection getConnection() throws SQLException {
        String url = "jdbc:oracle:thin:@localhost:1521:orcl";
        String user = "my_user";
        String password = "my_password";
        return java.sql.DriverManager.getConnection(url, user, password);
    }
}

答案 1 :(得分:0)

我不确定此限制来自何处。但是,Oracle JDBC Developer's Guide提出了以下建议:

  

Oracle建议将批大小保持在100或更小范围内。较大的批次几乎没有或根本没有改善性能,并且由于处理大量批次所需的客户端资源,实际上可能会降低性能。

当然可以使用更大的批处理大小,但不一定像您所看到的那样提高性能。一个人应该使用最适合用例和所用JDBC驱动程序/数据库的批处理大小。您可能应该在每次使用2500个批次的情况下才能获得最佳性能收益。