Oracle:批量收集性能

Posted

技术标签:

【中文标题】Oracle:批量收集性能【英文标题】:Oracle: Bulk Collect performance 【发布时间】:2012-07-12 06:50:04 【问题描述】:

你能帮我理解这句话吗?

如果没有批量绑定,PL/SQL 会向 SQL 引擎发送一条 SQL 语句 对于插入、更新或删除的每条记录,导致 影响性能的上下文切换。

【问题讨论】:

【参考方案1】:

在 Oracle 中,有一个 SQL 虚拟机 (VM) 和一个 PL/SQL VM。当您需要从一个 VM 移动到另一个 VM 时,会产生上下文转换的成本。单独而言,这些上下文转换相对较快,但是当您进行逐行处理时,它们加起来会占代码花费的时间的很大一部分。当您使用批量绑定时,您只需一次上下文转移即可将多行数据从一个 VM 移动到另一个 VM,从而显着减少上下文转移的次数,从而使您的代码更快。

以显式游标为例。如果我写这样的东西

DECLARE
  CURSOR c 
      IS SELECT *
           FROM source_table;
  l_rec source_table%rowtype;
BEGIN
  OPEN c;
  LOOP
    FETCH c INTO l_rec;
    EXIT WHEN c%notfound;

    INSERT INTO dest_table( col1, col2, ... , colN )
      VALUES( l_rec.col1, l_rec.col2, ... , l_rec.colN );
  END LOOP;
END;

那么每次我执行 fetch 时,我都是

执行从 PL/SQL VM 到 SQL VM 的上下文转移 要求SQL VM执行游标生成下一行数据 执行另一个从 SQL VM 到 PL/SQL VM 的上下文转移以返回我的单行数据

每次我插入一行时,我都在做同样的事情。我承担了将一行数据从 PL/SQL VM 传送到 SQL VM、要求 SQL 执行 INSERT 语句的上下文转移成本,然后产生了另一个上下文转移回 PL/ 的成本SQL。

如果source_table 有 100 万行,那就是 400 万次上下文转换,这可能占我的代码已用时间的合理比例。另一方面,如果我使用 100 的 LIMIT 执行 BULK COLLECT,我可以通过每次将 SQL VM 中的 100 行数据检索到 PL/SQL 中的集合中来消除 99% 的上下文转换。每次我在目标表中发生上下文转换时,都会产生上下文转换和向目标表中插入 100 行的成本。

如果可以重写我的代码以使用批量操作

DECLARE
  CURSOR c 
      IS SELECT *
           FROM source_table;
  TYPE  nt_type IS TABLE OF source_table%rowtype;
  l_arr nt_type;
BEGIN
  OPEN c;
  LOOP
    FETCH c BULK COLLECT INTO l_arr LIMIT 100;
    EXIT WHEN l_arr.count = 0;

    FORALL i IN 1 .. l_arr.count
      INSERT INTO dest_table( col1, col2, ... , colN )
        VALUES( l_arr(i).col1, l_arr(i).col2, ... , l_arr(i).colN );
  END LOOP;
END;

现在,每次执行 fetch 时,我都会通过一组上下文转换将 100 行数据检索到我的集合中。每次我执行FORALL 插入时,我都会插入 100 行并带有一组上下文转换。如果source_table 有 100 万行,这意味着我已经从 400 万个上下文转换变为 40,000 个上下文转换。如果上下文转换占我的代码运行时间的 20%,那么我已经消除了 19.8% 的运行时间。

您可以增加LIMIT 的大小以进一步减少上下文转换的数量,但您很快就会遇到收益递减规律。如果您使用 1000 而不是 100 的 LIMIT,您将消除 99.9% 的上下文转换,而不是 99%。但是,这意味着您的集合使用了 10 倍以上的 PGA 内存。在我们的假设示例中,它只会多消除 0.18% 的经过时间。通过消除额外的上下文转换,您很快就会达到您使用的额外内存增加的时间多于节省的时间的地步。一般来说,介于 100 到 1000 之间的 LIMIT 可能是最佳位置。

当然,在此示例中,消除所有上下文转换并在单个 SQL 语句中完成所有操作会更有效

INSERT INTO dest_table( col1, col2, ... , colN )
  SELECT col1, col2, ... , colN
    FROM source_table;

只有在您对源表中的数据进行某种无法在 SQL 中合理实现的操作时,首先求助于 PL/SQL 才有意义。

此外,我在示例中有意使用了显式游标。如果您使用隐式游标,在最新版本的 Oracle 中,您将获得 BULK COLLECT 和隐式 100 的 LIMIT 的好处。还有另一个 *** 问题讨论了相关的 performance benefits of implicit and explicit cursors with bulk operations,它更详细地介绍了这些特定的皱纹。

【讨论】:

【参考方案2】:

据我了解,涉及两个引擎,PL/SQL engine and SQL Engine。一次执行一个使用一个引擎的查询比在两个引擎之间切换更有效

例子:

  INSERT INTO t VALUES(1)

由SQL引擎处理

  FOR Lcntr IN 1..20

  END LOOP

由PL/SQL引擎执行

如果你结合上面的两个语句,把 INSERT 放在循环中,

FOR Lcntr IN 1..20
  INSERT INTO t VALUES(1)
END LOOP

对于每 (20) 次迭代,Oracle 将在两个引擎之间切换。 在这种情况下,建议使用 BULK INSERT,它在整个执行过程中都使用 PL/SQL 引擎

【讨论】:

你的最后一句话有点骗人。 BULK 使上下文切换只发生一次,尽管它仍然会发生。

以上是关于Oracle:批量收集性能的主要内容,如果未能解决你的问题,请参考以下文章

Oracle批量导入数据时的在线统计信息收集

Oracle批量收集成三列

使用 LOOP 将 Oracle 批量收集到集合中

从 Oracle 游标批量收集列的子集

在 Oracle 中对集合类型“对象”进行批量收集

Oracle:使用 select 插入不返回批量收集新插入的 id