【问题标题】:Performance improvement of plsql using Bulk collect使用 Bulk collect 提高 plsql 的性能
【发布时间】:2019-07-02 04:16:27
【问题描述】:

我正在使用批量收集来缩短执行时间。当我不使用批量收集时,它会在 4 分钟内执行。 但是当我使用批量收集时没有输出,控制台中也没有显示错误消息。我可以看到创建了一个空白假脱机文件。 如果我错误地使用了bulk collect,请告诉我,我们可以在select语句中使用这个子句并限制吗? 表包含最多 100 万条记录。

SET SERVEROUTPUT ON FORMAT WRAPPED
SET VERIFY OFF
SET FEEDBACK OFF
SET TERMOUT OFF

SPOOL C:\Temp\spool_1.txt

DECLARE

  cursor c2 is (
    select count(distinct e.cdb_pref_event_id)
          ,e.supp_cd
      from (select distinct eh.cdb_customer_id   cdb_customer_id
                           ,eh.cdb_pref_event_id cdb_pref_event_id
                           ,eh.supp_cd           supp_cd
              from (select *
                      from cdb_stg.cpm_pref_event_stg_arc
                     where trunc(load_date) = trunc(sysdate - 1)) eh
              Left outer join cdb_admin.cpm_pref_result er on (eh.cdb_customer_id =
                                                              er.cdb_customer_id and
                                                              eh.cdb_pref_event_id =
                                                              er.cdb_pref_event_id)
             where er.cdb_pref_event_id is null
               and er.cdb_customer_id is null) r
      join cdb_admin.cpm_pref_event_exception e on (r.cdb_customer_id =
                                                   e.cdb_customer_id and
                                                   r.cdb_pref_event_id =
                                                   e.cdb_pref_event_id)
     group by e.supp_cd);

  TYPE totalprefresults is table of NUMBER(20);
  TYPE supcd_1 is table of cdb_admin.cpm_pref_event_stg.supp_cd%TYPE;
  total_prefresults totalprefresults;
  supcd1            supcd_1;
  --Total_prefresults NUMBER(20);
  --SUPCD1 CDB_ADMIN.CPM_PREF_EVENT_STG.supp_cd%TYPE;
  profile_counts NUMBER(20);

  iter Integer := 0;

BEGIN

  select count(distinct cdb_customer_id)
    into profile_counts
    from cdb_admin.cpm_pref_event_exception h
   where cdb_customer_id in
         (Select distinct e.cdb_customer_id
            from (Select distinct eh.cdb_customer_id   cdb_customer_id
                                 ,eh.cdb_pref_event_id cdb_pref_event_id
                                 ,eh.supp_cd           supp_cd
                    from (select *
                            from cdb_stg.cpm_pref_event_stg_arc
                           where trunc(load_date) = trunc(sysdate - 1)) eh
                    Left outer join cdb_admin.cpm_pref_result er on (eh.cdb_customer_id =
                                                                    er.cdb_customer_id and
                                                                    eh.cdb_pref_event_id =
                                                                    er.cdb_pref_event_id)
                   where er.cdb_pref_event_id is null
                     and er.cdb_customer_id is null) r
            join cdb_admin.cpm_pref_event_exception e on (r.cdb_customer_id =
                                                         e.cdb_customer_id and
                                                         r.cdb_pref_event_id =
                                                         e.cdb_pref_event_id)
           where e.supp_cd = 'PROFILE-NOT-FOUND')
     and h.supp_cd != 'PROFILE-NOT-FOUND';

  dbms_output.put_line('TOTAL EVENTS VALIDATION');
  dbms_output.put_line('-------------------------------------------------------------');
  dbms_output.put_line('');

  dbms_output.put_line(rpad('Pref_Counts', 25) || rpad('Supp_CD', 25));

  OPEN c2;
  LOOP
    FETCH c2 BULK COLLECT
      INTO total_prefresults
          ,supcd1 limit 100;
    EXIT WHEN c2%NOTFOUND;
    dbms_output.put_line(rpad(total_prefresults, 25) || rpad(supcd1, 25));

    IF (supcd1 = 'PROFILE-NOT-FOUND')
    then
      dbms_output.put_line('');
      dbms_output.put_line('Profile not found records count : ' ||
                           total_prefresults);

      dbms_output.put_line(profile_counts ||
                           ' : counts moved to other exceptions ');
      dbms_output.put_line((total_prefresults - profile_counts) ||
                           ' : are still in Profile_not_found exception');

    END IF;

    iter := iter + 1;
  END LOOP;
  CLOSE c2;
  dbms_output.put_line('');
  dbms_output.put_line('Number of missing Records: ' || iter);

END;
/
SPOOL OFF

【问题讨论】:

  • total_prefresultssupcd1 是集合,可以包含多个元素。您可以在第一个 Loop 中添加一个循环并遍历集合的元素。但我认为你不会那样改进你的代码。

标签: sql oracle plsql


【解决方案1】:

我认为瓶颈是这种情况:where trunc(load_date) = trunc(sysdate - 1)

你有trunc(load_date) 的索引吗?在 trunc(load_date) 上创建基于函数的索引,或者如果您已经在 load_date 上创建了索引,那么请尝试

WHERE load_date >= trunc(sysdate - 1) AND load_date < trunc(sysdate)

还要检查您的查询是否真的需要distinct。如果可能,请删除它们。

【讨论】:

    【解决方案2】:

    我已将您的代码从OPEN c2; 改写为CLOSE c2;

    BULK COLLECT 应该执行一次以将所有数据存储在集合中一次(一次),然后可以使用FOR loop 中的索引(即以下情况下的 I)使用此集合,如下所示:

    OPEN C2; 
    FETCH C2 BULK COLLECT INTO
        TOTAL_PREFRESULTS,
        SUPCD1;
    --EXIT WHEN C2%NOTFOUND;
    CLOSE C2;
    
    -- To list down all the values before processing the logic
    FOR I IN TOTAL_PREFRESULTS.FIRST..TOTAL_PREFRESULTS.LAST LOOP
    DBMS_OUTPUT.PUT_LINE(RPAD(TOTAL_PREFRESULTS(I), 25)
                         || RPAD(SUPCD1(I), 25));
    END LOOP;
    
    
    FOR I IN TOTAL_PREFRESULTS.FIRST..TOTAL_PREFRESULTS.LAST LOOP
        IF ( SUPCD1(I) = 'PROFILE-NOT-FOUND' ) THEN
            DBMS_OUTPUT.PUT_LINE('');
            DBMS_OUTPUT.PUT_LINE('Profile not found records count : ' || TOTAL_PREFRESULTS(I));
            DBMS_OUTPUT.PUT_LINE(PROFILE_COUNTS || ' : counts moved to other exceptions ');
            DBMS_OUTPUT.PUT_LINE((TOTAL_PREFRESULTS(I) - PROFILE_COUNTS)
                                 || ' : are still in Profile_not_found exception');
        END IF;
    
        ITER := ITER + 1;
    END LOOP;
    

    在你的代码中替换上面的代码 sn-p 并尝试执行。

    请参考guide to use BULK COLLECT

    干杯!!

    【讨论】:

      【解决方案3】:

      批量收集可以提供可观的性能提升。然而,有几个问题涉及。
      首先,%notfound 的含义有所不同。 在标准游标上 %notfound 意味着所有行都已被提取并且没有更多行。使用批量收集此更改为“没有足够的行来达到指定的 LIMIT(如果存在)。 这并不意味着没有获取任何行,只是没有达到指定的限制。例如,如果您的限制是 100 并且仅检索到 50,则 %notfound 将返回 True。这是参考指南失败的地方。
      第二个是没有限制子句的情况:游标中的所有行都返回到共享内存(PGA?)。那有什么问题。 如果有 100 行或 1000 行,那么您很可能是 k,但假设有 100,000 或 1M 行,它们仍然全部加载到内存中。最后(至少现在)当使用 limit 子句时,整个 Fetch+Process 本身必须包含在一个循环中,或者您只处理第一个 fetch - 意味着仅指定的限制行数 - 无论实际存在多少行。参考指南失败的另一点。 以下骨架可容纳上述内容。

          declare 
             max_bulk_rows constant integer  := 1000;  -- define the max number of rows for each fetch ... 
      
             cursor c_bulk is(
              Select ... ;
      
             type bulk_row_t is table of c_bulk%rowtype; 
             bulk_row  bulk_row_t; 
      
          Begin 
             open c_bulk;  
             loop
                 fetch c_bulk                      -- fill buffer 
                 bulk collect into  bulk_row
                 limit max_bulk_row; 
      
                 for i in bulk_row.first .. bulk_row.last -- process each row in buffer 
                 loop
                     "process individual row here"
                 end loop; 
      
                 foreach ...                      -- bulk output of rows here is needed.
      
                 exit when bulk_row.count < max_bulk_row;  -- exit process loop if all rows processed
      
             end loop ;   -- loop back and fetch next buffer if needed
         close c_bulk;
          ...
         end; 
      

      【讨论】:

      • 感谢您的意见。不幸的是,我没有标记多个答案的选项。
      • 没问题。下次只需将模板塞入你的 bog-o-tricks 中即可。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多