【问题标题】:FORALL usage in BULK COLLECT in PLSQLPLSQL 中 BULK COLLECT 中的 FORALL 用法
【发布时间】:2021-09-28 07:56:04
【问题描述】:

您好,我有一个程序和问题。此过程用于提取数据,然后将它们插入到一个表中。我意识到我们在 FORALL 语法中不使用循环。所以我的目的是提取 10.000 条记录,然后将这 10.000 条记录插入到一​​个表中。我想知道的是,该过程只是在插入 10.000 条记录后停止还是继续其工作?我问是因为 FORALL 内部没有循环吗? 我的第二个问题是我应该删除脚本中的 v_cnt 和 IF MOD (v_cnt, 10000) = 0 行吗?

我在下面添加脚本,我愿意接受任何修复建议或其他任何事情,谢谢。

    create or replace procedure GPU_DATA_EXTRACTOR_TEST(pid_billdate DATE) is
v_cnt      NUMBER;
c_limit   CONSTANT PLS_INTEGER DEFAULT 10000;

CURSOR c1 IS
SELECT DISTINCT intl_prod_id
      FROM apld_bill_rt abr,
           acct_bill ab
      WHERE abr.CHRG_TP = 'INSTALLMENT'
          AND abr.TAX_CATG_ID = 'NOTAX'
          AND abr.acct_bill_id = ab.acct_bill_id
          AND ab.bill_date = pid_billdate;

TYPE prod_ids_t IS TABLE OF apld_bill_rt.intl_prod_id%TYPE INDEX BY PLS_INTEGER;
l_prod_ids   prod_ids_t;
begin

   execute immediate 'truncate table GPU_INV_TEST';

   v_cnt := 0;
  OPEN c1;

  LOOP
     FETCH c1 BULK COLLECT INTO l_prod_ids LIMIT c_limit;
     
     EXIT WHEN l_prod_ids.COUNT = 0;

    FORALL indx IN 1 .. l_prod_ids.COUNT
    
      INSERT INTO GPU_INV_TEST
         SELECT AB.ACCT_BILL_ID,
                AB.BILL_NO,
                AB.INV_ID,
                AB.BILL_DATE,
                ba2.bill_acct_id,
                ba1.bill_acct_id parent_bill_acct_id,
                AB.DUE_DATE,
                PG.CMPG_ID,
                ABR.NET_AMT,
                AB.DUE_AMT,
                P.PROD_NUM,
                pds.DST_ID,
                ABR.DESCR,
                p.intl_prod_id
           FROM apld_bill_rt abr,
                acct_bill ab,
                prod p,
                FCBSADM.PROD_DST pds,
                bill_acct_prod bap,
                bill_acct ba1,
                bill_acct ba2,
                prod_cmpg pg
          WHERE ab.intl_bill_acct_id = ba1.intl_bill_acct_id
                AND AB.ACCT_BILL_ID = ABR.ACCT_BILL_ID
                AND ba1.intl_bill_acct_id = ba2.parent_bill_acct_id
                AND ba2.intl_bill_acct_id = bap.intl_bill_acct_id
                AND bap.intl_prod_id = abr.intl_prod_id
                AND ABR.CHRG_TP = 'INSTALLMENT'
                AND bap.intl_prod_id = pds.intl_prod_id
                AND bap.intl_prod_id = p.intl_prod_id
                AND p.intl_prod_id = pg.intl_prod_id(+)
                AND ABR.intl_prod_id = l_prod_ids(indx)
UNION
    SELECT AB.ACCT_BILL_ID,
                AB.BILL_NO,
                AB.INV_ID,
                AB.BILL_DATE,
                ba1.bill_acct_id,
                ba1.bill_acct_id parent_bill_acct_id,
                AB.DUE_DATE,
                PG.CMPG_ID,
                ABR.NET_AMT,
                AB.DUE_AMT,
                P.PROD_NUM,
                pds.DST_ID,
                ABR.DESCR,
                p.intl_prod_id
           FROM apld_bill_rt abr,
                acct_bill ab,
                prod p,
                FCBSADM.PROD_DST pds,
                bill_acct_prod bap,
                bill_acct ba1,
                prod_cmpg pg
          WHERE ab.intl_bill_acct_id = ba1.intl_bill_acct_id
                AND AB.ACCT_BILL_ID = ABR.ACCT_BILL_ID
                --AND ba1.intl_bill_acct_id = ba2.parent_bill_acct_id
                AND ba1.intl_bill_acct_id = bap.intl_bill_acct_id
                AND bap.intl_prod_id = abr.intl_prod_id
                AND ABR.CHRG_TP = 'INSTALLMENT'
                AND bap.intl_prod_id = pds.intl_prod_id
                AND bap.intl_prod_id = p.intl_prod_id
                AND p.intl_prod_id = pg.intl_prod_id(+)
                AND ABR.intl_prod_id = l_prod_ids(indx);

      v_cnt := v_cnt + 1;

      IF MOD (v_cnt, 10000) = 0
      THEN
         COMMIT;
      END IF;
    COMMIT;  
    
    
    END LOOP;
   CLOSE c1;
end;

【问题讨论】:

    标签: oracle plsql


    【解决方案1】:

    您的程序处理所有内容,它只处理 10000 条记录的批量,因此容易出现并行性。

    forall中有一个循环,对所有记录(10K)都会执行insert

    关于 v_cnt mod,你可以删除它(但不建议这样做),除了性能之外没有任何问题,原作者很可能发现重做段可能会变得非常大,所以他只是每隔一段时间就提交一次(但因为他一开始就截断了表格,这根本不是问题)。

    【讨论】:

    • 您好,感谢您的评论。我有近 280.000 条记录符合上述脚本中的条件。那么它会只需要 10.000 然后停止还是每次都需要 10.000 并继续(如数据块 10.000 和下一个 10.000 继续)?你怎么看?我是这份工作的新手,这就是为什么要问:)
    • 正如我所说,它应该覆盖整个区域,但以 10K 块工作,因此,如果需要,这可能会并行工作,直到 280K 完成后才会停止。您可以随时检查结果插入的大小,您应该在那里找到您的记录。
    • 这里的批量收集没有任何意义。光标只得到一个值,可以使用 cte 表达式与下面的选择连接。
    • Bulk 用于帮助并行性,据我所知仅此而已,它表明每个进程都可以独立于其他进程处理“批量记录”。
    • 批量收集是一种获取数据的方法,其中 PL/SQL 引擎告诉 SQL 引擎一次收集许多行并将它们放在一个集合中。 SQL 引擎检索所有行并将它们加载到集合中,然后切换回 PL/SQL 引擎。仅使用 2 个上下文切换来检索所有行。这里没有并行工作,是一种不同且更有效的游标工作方式。与并行 DML 或使用 DBMS_PARALLEL_EXECUTE 的并行处理无关。
    【解决方案2】:

    您不需要在该过程中执行任何bulk collect。这是没有意义的。为什么 ?因为您没有在类型中收集值并对它们应用逻辑,所以您只需在光标中获取 distinct intl_prod_id 并为每个值进行选择,这可以通过 CTE 表达式轻松解决。

    如果您也使用直接路径,通过应用提示append,那么您将获得更好的程序和巨大的性能提升。

    请记住,对于游标中的每一行,您都需要运行插入/选择,这意味着为游标中的每个值连接操作中涉及的所有表。尽量应用原理,越简单越好。

    create or replace procedure GPU_DATA_EXTRACTOR_TEST(pid_billdate DATE) is
    
    begin
        execute immediate 'truncate table GPU_INV_TEST';
    
       INSERT /*+append */ INTO GPU_INV_TEST
       with x as 
       (  SELECT /*+materialize */ DISTINCT intl_prod_id as intl_prod_id 
          FROM apld_bill_rt abr,
               acct_bill ab
          WHERE abr.CHRG_TP = 'INSTALLMENT'
              AND abr.TAX_CATG_ID = 'NOTAX'
              AND abr.acct_bill_id = ab.acct_bill_id
              AND ab.bill_date = pid_billdate 
        )
          SELECT AB.ACCT_BILL_ID,
                    AB.BILL_NO,
                    AB.INV_ID,
                    AB.BILL_DATE,
                    ba2.bill_acct_id,
                    ba1.bill_acct_id parent_bill_acct_id,
                    AB.DUE_DATE,
                    PG.CMPG_ID,
                    ABR.NET_AMT,
                    AB.DUE_AMT,
                    P.PROD_NUM,
                    pds.DST_ID,
                    ABR.DESCR,
                    p.intl_prod_id
               FROM apld_bill_rt abr,
                    acct_bill ab,
                    prod p,
                    FCBSADM.PROD_DST pds,
                    bill_acct_prod bap,
                    bill_acct ba1,
                    bill_acct ba2,
                    prod_cmpg pg , 
                    x
              WHERE ab.intl_bill_acct_id = ba1.intl_bill_acct_id
                    AND AB.ACCT_BILL_ID = ABR.ACCT_BILL_ID
                    AND ba1.intl_bill_acct_id = ba2.parent_bill_acct_id
                    AND ba2.intl_bill_acct_id = bap.intl_bill_acct_id
                    AND bap.intl_prod_id = abr.intl_prod_id
                    AND ABR.CHRG_TP = 'INSTALLMENT'
                    AND bap.intl_prod_id = pds.intl_prod_id
                    AND bap.intl_prod_id = p.intl_prod_id
                    AND p.intl_prod_id = pg.intl_prod_id(+)
                    AND ABR.intl_prod_id = x.intl_prod_id
        UNION
        SELECT AB.ACCT_BILL_ID,
                    AB.BILL_NO,
                    AB.INV_ID,
                    AB.BILL_DATE,
                    ba1.bill_acct_id,
                    ba1.bill_acct_id parent_bill_acct_id,
                    AB.DUE_DATE,
                    PG.CMPG_ID,
                    ABR.NET_AMT,
                    AB.DUE_AMT,
                    P.PROD_NUM,
                    pds.DST_ID,
                    ABR.DESCR,
                    p.intl_prod_id
               FROM apld_bill_rt abr,
                    acct_bill ab,
                    prod p,
                    FCBSADM.PROD_DST pds,
                    bill_acct_prod bap,
                    bill_acct ba1,
                    prod_cmpg pg, 
                    x
              WHERE ab.intl_bill_acct_id = ba1.intl_bill_acct_id
                    AND AB.ACCT_BILL_ID = ABR.ACCT_BILL_ID
                    --AND ba1.intl_bill_acct_id = ba2.parent_bill_acct_id
                    AND ba1.intl_bill_acct_id = bap.intl_bill_acct_id
                    AND bap.intl_prod_id = abr.intl_prod_id
                    AND ABR.CHRG_TP = 'INSTALLMENT'
                    AND bap.intl_prod_id = pds.intl_prod_id
                    AND bap.intl_prod_id = p.intl_prod_id
                    AND p.intl_prod_id = pg.intl_prod_id(+)
                    AND ABR.intl_prod_id = x.intl_prod_id;
    
             COMMIT;
    end;
    

    【讨论】:

    • 如果您不想使用并行性并希望按顺序执行,这是最好的解决方案,但通常,当处理超过 10 万条记录时,并行工作会更有效,在这种情况下应该删除 APPEND 提示,并且根据业务规则,批量工作可能会更好(就像原始开发人员所做的那样)
    • @Gar,您没有将并行工作与批量收集一起使用,因为对于游标中的每条记录,您都在基于连接进行插入。如果您这样做 100k 次,则需要运行连接 100k 次。在这种情况下,一个普通的插入选择可以一步完成,而 10 万条记录对 Oracle 来说毫无意义。此外,这不是顺序的,它是一个整体过程,就像任何插入选择一样
    • 另外,谁反对这个答案表明他/她对甲骨文一无所知。如果您没有对批量收集值应用任何逻辑,并且您可以以一种简单易行的方式进行操作,那么请应用基本开发原则之一:越简单越好
    • 如果添加 APPEND,查询将不会并行,因此如果您有 48 个内核,则无论默认并行度如何,只有 1 个内核可以工作。如果您按原样运行查询,它可能会并行触发,但可以(并且我坚持可以)杀死重做日志。如果您按原样运行 OP 查询,它可能会并行触发并且不会损害重做。所以这三种方式都是有效的,取决于你想要什么:更简单的代码、核心使用效率、重做等
    • 如果添加 APPEND,查询将进入直接路径,绕过缓冲区缓存。重做日志不会被杀死,因为直接路径操作的重做消耗较少。代码在开始之前截断了表,这意味着那里的数据是瞬态的。您也可以添加提示 parallel 和 nologging,但这里的要点是,这样做您运行单个选择,使用批量收集运行选择,所有连接的次数与游标中的值一样多。顺便说一句,300M 的正常默认重做日志文件不会被 100k 行杀死。曾经
    猜你喜欢
    • 2011-12-31
    • 1970-01-01
    • 2020-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多