【问题标题】:JDBC Batch insert into Oracle Not workingJDBC批量插入Oracle不工作
【发布时间】:2019-01-06 10:43:13
【问题描述】:

我正在使用 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 代码来说明发生了什么:

环境

$ 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 Enterprise Edition Release 11.2.0.4.0 - 64bit

多次运行查询没有帮助 - 结果相同。 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;

【问题讨论】:

  • 也许您应该尝试以不同的执行次数运行您的示例代码(并比较它们)。您也可以从另一侧调查视图 v$sql - 列执行,rows_processed。
  • 感谢您的评论。我更新了描述,我回答你的问题了吗?
  • PS:你对目标表有任何触发器或约束吗?在 OCI api 的情况下,这变得很重要,因为 Oracle 返回一个值向量,并且您必须检查每个表的批量操作是否成功。
  • 不,我不知道。非常简单的表,没有约束或触发器

标签: oracle jdbc batching


【解决方案1】:

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

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

当然可以使用更大的批量大小,但它们并不一定会像您所见的那样提高性能。应该使用最适合用例和所使用的 JDBC 驱动程序/数据库的批量大小。在您的案例中,您可能应该使用 2500 个批次来获得最佳性能优势。

【讨论】:

  • 感谢您的回复! 100 批量非常小。我尝试过大小​​为 2500 的批次(甚至在不同的连接中)。 JDBC 驱动程序在第一次插入后 2500 下一个块一次插入一行。
  • “100 批大小非常小”如果我想插入数百万行
  • @referee 您尝试过小批量吗?较小的批次会有所帮助,因为与收集和发送数千行相比,它的开销更少。批量大小是减少网络往返延迟开销与增加内存使用和垃圾收集开销之间的平衡。
  • @MarkRotteveel 我想我找到了真正的问题,请检查我更新的问题。很抱歉给大家带来了困扰
  • @referee 不要将解决方案添加到您的问题中,而是考虑将其发布为答案。这就是应该如何使用堆栈溢出:),作为奖励,在超时后,您可以接受自己的答案。
【解决方案2】:

问题解决了

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

我之前的例子没有描述真正的问题。很抱歉没有立即给出全貌。
我将其简化为无法处理空值的状态。
请检查上面的示例,我已对其进行了更新。
如果我使用java.sql.Types.NULL Oracle JDBC 驱动程序使用theVarcharNullBinder 作为null 值-它会以某种方式导致如此奇怪的工作。我认为 Driver 被批量使用,直到第一个 null 没有指定类型,在 null 之后它回退到一个一个插入。

number 的列驱动程序更改为 java.sql.Types.NUMERIC 后,使用 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);
    }
}

【讨论】:

  • 只是出于好奇:你为什么使用setBigDecimal,如果你使用setBigDecimal(1, null)而不是setNull(1, Types.NUMERIC)会发生什么?
  • 如果我正确查看了 Oracle JDBC 代码,setBigDecimal(1, null)setNull(1, Types.NUMERIC) 是相等的。但是这段代码也用于其他数据库(不是 Oracle),当我们不使用 ps.setNull() 时,我们遇到了一些 SQL Server 问题(这是很久以前的事了,我可能错了)
猜你喜欢
  • 2019-06-24
  • 1970-01-01
  • 2018-06-17
  • 2011-01-14
  • 2012-01-17
  • 1970-01-01
  • 2011-02-28
相关资源
最近更新 更多