【问题标题】:Efficiently inserting variable number of rows into Oracle from .NET Application从 .NET 应用程序有效地将可变数量的行插入 Oracle
【发布时间】:2013-12-12 17:11:35
【问题描述】:

我的 .NET 应用程序接收到一个数据流,我需要将其转储到 Oracle 数据库中的几个表中。我有一个内部队列,它对数据进行排队,并且从队列中读取的几个线程将其转换为相应的插入语句。我每秒接收大约 10 个数据项,但每秒可能会爆发超过 100 个数据项。

我的第一种方法是把每一个数据项,转换成对应的插入语句,一个一个地执行。但是,这太慢了,因为每次插入都需要往返数据库。

我的下一个方法是将插入批处理成最多 50 个的组,具体取决于队列中有多少项,然后将它们包装到一个 begin-end 块中,然后将其推送到数据库中,如下所示:

begin
  insert into MyTable (col1, col2, col3...) values (123, 'data1', 'data2', ...);
  insert into MyTable (col1, col2, col3...) values (456, 'dataX', 'dataY', ...);
  insert into MyTable (col1, col2, col3...) values (789, 'dataA', 'dataB', ...);
  -- variable number inserts...
end;

这显着提高了性能,我很高兴。然后我们的 Oracle 人员来找我,告诉我我正在杀死他的数据库,因为我正在发送大量独特的 SQL 语句,Oracle 每次都必须解析和缓存这些语句。最终,甲骨文崩溃了。建议是始终对绑定变量使用相同的 SQL 语句,这样就不必每次都对其进行解析。

但是,这会让我回到最初遇到的问题,即我必须一次运行每个插入语句,并使用绑定变量,以使语句相同。

insert into MyTable (col1, col2, col3...) values (:val1, :val2, :val3, ...);

我可以尝试将多个插入合并到一个开始-结束块中,但这会导致 SQL 语句都是唯一的问题。

begin
  insert into MyTable (col1, col2, col3...) values (:val11, :val12, :val13, ...);
  insert into MyTable (col1, col2, col3...) values (:val21, :val22, :val23, ...);
  insert into MyTable (col1, col2, col3...) values (:val31, :val32, :val33, ...);
  ...
end;

我该怎么办?使用绑定变量一一插入语句,但使用大量线程?我应该将它们写入 CSV 文件并使用 SQL Loader 吗?但是我将如何处理 CLOB 列?是否应该将插入内容包装在存储过程中,然后使用我的批处理方法?

我觉得这一定是一个很普遍的问题,对于这种情况一定有某种最佳实践。

【问题讨论】:

  • 在 SQL 中可以有多个值 (1,2), (2,3) ... 这减少了唯一语句的数量和锁的数量。有 1000 个限制和一个甜蜜点。怀疑与 Oracle 相同。还有TVP和Drapper。但我的经验是使用 SQL,所以只能发表评论。
  • 根本不是我的领域,但这里有一个关于使用数组绑定在 ODP.Net 中提高批量插入效率的页面:dotnetslackers.com/articles/ado_net/…

标签: .net oracle


【解决方案1】:

我正在寻找的是称为数组绑定,如here for ODP.NEThere for Devart 所述。感谢 Morbo 为我指明了正确的方向。

基本上,您可以将一组值绑定到绑定变量,并在一次调用中执行多个插入。

例如对于下面的插入语句

insert into MyTable (col1, col2, col3) values (:val1, :val2, :val3)

您可以使用数组绑定插入多行。以下示例将通过单次往返将 3 行插入数据库。 (请注意,此代码用于 Devart Oracle 连接器。ODP.NET 代码看起来会有些不同)。

var command = connection.CreateCommand();
command.CommandText = "insert into MyTable (col1, col2, col3) values (:val1, :val2, :val3)"
command.Parameters.Add("val1", OracleDbType.Number, 10);
command.Parameters.Add("val2", OracleDbType.VarChar, 4000);
command.Parameters.Add("val3", OracleDbType.VarChar, 4000);
command.Parameters["val1"].Value = new int[] { 1, 2, 3 };
command.Parameters["val2"].Value = new int[] { "a", "b", "c" };
command.Parameters["val3"].Value = new int[] { "x, "y", "z" };
command.ExecuteArray(3);

我使用 Devart 连接器进行了一些快速的性能测量。 Oracle 客户端的结果可能会有所不同。

  • 对于 CLOB 数据,与逐个插入相比,似乎没有太大的改进。奇怪的是,当尝试插入超过 200 行时,数组绑定变慢了。这可能是 Devart 连接器的一个怪癖。
  • 在没有 CLOB 数据的情况下,与逐个插入相比,性能提高了 4 到 8 倍。

【讨论】:

    【解决方案2】:

    我只是 PL/SQL 开发人员,所以我不能告诉你很多关于 .net 的事情,只能帮助你处理 PLSQL。

    如果您尝试始终使用 50 个 插入来解决此问题,会发生什么情况?或者其他合适的量? 在这种情况下,你也会有这个块:

    begin
      insert into MyTable (col1, col2, col3...) values (:val11, :val12, :val13, ...);
      insert into MyTable (col1, col2, col3...) values (:val21, :val22, :val23, ...);
      insert into MyTable (col1, col2, col3...) values (:val31, :val32, :val33, ...);
      -- + 47 more inserts...
    
    exception
      when <some exception> then
        commit;  -- also you can write the log here about the error
    end;
    

    例外情况你可以使用重复索引not null约束,随便你!

    例如你有 37 行。您绑定所有这些,现在仅绑定 13 个空值,对于 col1,您绑定一些字符串,例如'一种'。在第 38 行,Oracle 引发了一个异常(在本例中为 invalid_number),它提交了结果。

    在这种情况下,您只需解析一次:在脚本的第一次运行时。因为 SQL 文本没有改变,所以以后的每一次运行都会被缓存。

    您可以针对 forall 语法增强此脚本以获得更好的性能。

    【讨论】:

      【解决方案3】:

      我会推荐这样的东西:

      DECLARE
         /*If you'll be populating all the columns of target table*/
         TYPE t_table_col IS TABLE OF table1%rowtype;
      
         /*OR If you'll be populating just some columns of the table */
         TYPE r_table  IS RECORD (
            column1 table1.column1%type,
            column2 table1.column2%type
         );
         TYPE t_table_col IS TABLE OF r_table;
      
      
         co_table_col t_table_col;
      BEGIN
         /* I don't know the way you're getting the input values. So this is just an example */
         FOR i IN 1..inputValue.count LOOP
            co_table_col(i).column1 := inputValue(i).column1;
            co_table_col(i).column2 := inputValue(i).column2;
         END LOOP;
      
         /* Then if your structure is the same as the table one */
         FORALL i IN indices of co_table_col
            INSERT INTO table1 VALUES co_table_col(i);
      
         COMMIT;
      
      
         /* OR if you are populating just some columns (using a different structure) */
         FORALL i IN indices of co_table_col
            INSERT INTO table1(column1, column2) VALUES (co_table_col(i).column1, co_table_col(i).column2);
      
         COMMIT;
      
      END;
      

      这为您带来了巨大的优势,因为您的代码将更具可读性,并且使用 FORALL 可以让您进行更快的插入。

      因为您将控制要插入的行数,所以可能不会遇到撤消空间或类似问题的问题。使用 FORALL,您可以保存异常,因此您可以知道某些插入是否失败等。

      【讨论】:

      • 这是要走的路。看看这个链接,它有一些关于这种方法的好信息。 oracle.com/technetwork/issue-archive/2012/12-sep/… 来自链接:“在我运行 Oracle Database 11g 第 2 版的笔记本电脑上,一次插入 100,000 行需要 4.94 秒。使用 FORALL,这 100,000 行在 0.12 秒内插入。”
      • 大声笑,你读过这个问题吗?问题在于(作者)将参数/SQL 传递给数据库,而不是快速插入。
      • 问题是:有效地将可变数量的行插入Oracle。 Omar 的解决方案是最有效的解决方案。
      • 感谢您的回答。我的问题的标题不够清楚。我的问题专门针对 .NET 应用程序。
      【解决方案4】:

      可能存在某种 .Net 批处理,类似于 JDBC batching

      如果没有,对代码稍作调整会有所帮助:

      insert into MyTable(col1, col2, col3...)
      select :val11, :val12, :val13, ... from dual union all
      select :val21, :val22, :val23, ... from dual union all
      ...
      select :val31, :val32, :val33, ... from dual
      

      现在只有一条 SQL 语句需要解析。注意不要批处理太多行。超过几百行可以导致slow parsing performance

      【讨论】:

      • 这没有任何好处:查询仍然不会被缓存
      • @Simon 确实不会缓存查询,但是只有一个查询而不是 50 个。它的解析时间可能比您的查询稍长,但它也少了 49 个上下文切换。另一种选择是将我们的两个答案结合起来——一个始终有 50 个select ... from dual 的查询。并且可能是一个内联视图,它可以过滤掉存在指示该行不是真实行的神奇值的行。
      • 是的,当然可以组合,但是这里提到的主要问题是缓存。
      • @Simon 是的,此解决方案将缓存(解析)的 SQL 量减少约 98%。我没有进行过测试,但我敢打赌,通常 1 个硬解析语句比 50 个软解析(或缓存)语句运行得更好。
      • 这不是关于语句,而是关于整段代码
      猜你喜欢
      • 1970-01-01
      • 2017-05-22
      • 2021-10-06
      • 1970-01-01
      • 2013-05-08
      • 1970-01-01
      • 1970-01-01
      • 2019-08-26
      • 1970-01-01
      相关资源
      最近更新 更多