从.NET应用程序有效地将可变数量的行插入Oracle

时间:2013-12-12 17:11:35

标签: .net oracle

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

我的第一种方法是获取每个数据项,将其转换为相应的insert语句,然后逐个执行。然而,事实证明这太慢了,因为每个插入都需要往返数据库。

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

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语句,以便语句是相同的。

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列?是否应该将插入包装在存储过程中,然后使用我的批处理方法?

我觉得这一定是一个非常普遍的问题,对于这种情况必须有某种最佳做法。

4 个答案:

答案 0 :(得分:2)

我正在寻找的是数组绑定,如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 Client可能会有不同的结果。

  • 使用CLOB数据,与逐个插入相比,似乎没有太大的改进。奇怪的是,当尝试插入超过200行时,数组绑定速度较慢。这可能是Devart连接器的一个怪癖。
  • 没有CLOB数据,与逐个插入相比,改进了4倍到8倍。

Performance Without CLOB Data

Performance With CLOB Data

答案 1 :(得分:1)

我只是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 约束,无论您喜欢什么!

E.g。你有37排。你绑定了所有这些,现在只用空值绑定13个,而对于col1你绑定一些字符串,例如'一个'。在第38行,Oracle引发了一个异常(在这种情况下为invalid_number),它提交结果。

在这种情况下,你只需解析一次:在第一次运行脚本时。每次进一步运行都将被缓存,因为SQL文本不会更改。

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

答案 2 :(得分:1)

我会推荐这样的东西:

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,您可以保存异常,这样您就可以知道某些插入是否失败等等。

答案 3 :(得分:1)

可能存在某种.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