Oracle:批量收集性能

标签 oracle bulk

你能帮我理解这句话吗?

Without the bulk bind, PL/SQL sends a SQL statement to the SQL engine for each record that is inserted, updated, or deleted leading to context switches that hurt performance.

最佳答案

在 Oracle 内部,有一个 SQL 虚拟机 (VM) 和一个 PL/SQL VM。当您需要从一个 VM 移动到另一个 VM 时,会产生上下文转换的成本。单独而言,这些上下文转换相对较快,但是当您进行逐行处理时,它们加起来会占代码花费时间的很大一部分。当您使用批量绑定(bind)时,您可以通过一次上下文转移将多行数据从一个 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;

然后每次我执行提取,我
  • 执行从 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 万次上下文转换,这可能占我代码运行时间的合理比例。另一方面,如果我执行 BULK COLLECTLIMIT 100,我可以消除 99% 的上下文转换,方法是每次我产生上下文转换的成本时从 SQL VM 将 100 行数据检索到 PL/SQL 中的集合中,并且每次我都向目标表中插入 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 的大小进一步减少上下文转换的数量,但您很快就会遇到 yield 递减规律。如果您使用了 LIMIT 1000 个而不是 100 个,您将消除 99.9% 的上下文转换,而不是 99%。但是,这意味着您的集合使用了 10 倍以上的 PGA 内存。在我们的假设示例中,它只会多消除 0.18% 的耗时。通过消除额外的上下文转换,您很快就会达到您使用的额外内存增加的时间多于节省的时间的地步。一般情况下,LIMIT介于 100 到 1000 之间的某个位置可能是最佳位置。

    当然,在这个例子中,消除所有上下文转换并在单个 SQL 语句中完成所有事情会更有效
    INSERT INTO dest_table( col1, col2, ... , colN )
      SELECT col1, col2, ... , colN
        FROM source_table;
    

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

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

    关于Oracle:批量收集性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11453215/

    相关文章:

    php - MySQL使用多个条件在单个查询中批量查询更新

    c# - 使用 ElasticLowLevelClient 客户端在 Elasticssearch 中批量索引

    sql - UPLOAD 语句包含 "dynamic"表名和列名? (甲骨文/JDE)

    c# - RDLC LocalReport 导出到 Excel 真的很慢

    oracle - 为什么此查询不使用 NLSSORT 索引?

    sql-server - SQL Server批量导入UTF-8数据格式文件

    sql - 使用选择和行锁更新查询

    java - 如何解密 WF_Decrypt

    php - 如何在 php 中无限制地发送数千封电子邮件?

    lucene - lucene批量更新策略?