JDBC 批量插入 Oracle 不工作

JDBC Batch insert into Oracle Not working

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

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

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 代码:

环境

$ 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 行 "batch" 插入 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;

我不确定这个限制是从哪里来的。但是,Oracle JDBC Developer's Guide 给出了这个建议:

Oracle recommends to keep the batch sizes in the range of 100 or less. Larger batches provide little or no performance improvement and may actually reduce performance due to the client resources required to handle the large batch.

当然可以使用更大的批量大小,但它们不一定会像您所看到的那样提高性能。应该使用最适合用例的批量大小,并使用 JDBC driver/DB。您可能应该在您的案例中使用 2500 个批次以查看最佳性能优势。

问题已解决

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

我前面的例子没有描述真正的问题。抱歉没能一下子给全图
我把它简化到这样一种状态,以至于我失去了对空值的处理。
请检查上面的例子我已经更新了它。
如果我使用 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);
    }
}