批量收集问题

Posted

技术标签:

【中文标题】批量收集问题【英文标题】:Issue with Bulk Collect 【发布时间】:2015-07-22 16:36:09 【问题描述】:

我有一个过程,我需要使用批量收集从游标中获取数据。这种方法的问题是 - 有时记录正在被处理,有时它们不是。我无法确定根本原因。当我尝试调试问题时,toad 没有响应并超时。这是代码。请帮忙!

PROCEDURE GetPendingItems(pprogramidlst IN VARCHAR2, EventCur OUT cur)
   AS
      vSessionId            NUMBER;
      vDefaultValue         svoltp.param.DefaultValue%TYPE;
      TYPE EventRec IS TABLE OF TrigEventQueue.TrigEventQueueId%TYPE;
      TYPE EventPartitionDate IS TABLE OF TrigEventQueue.PartitionDate%TYPE;
      vEventRec             EventRec;
      vEventPartitionDate   EventPartitionDate;
      vOldestPossibleDate   DATE;
      vtrigeventqueueid     VARCHAR2 (250);
      vprogramidlst         VARCHAR2 (250);
      vETInterval       number;
      VCOUNT            NUMBER;
      VCOUNT1 NUMBER;
      CURSOR PRCURSOR(vCount1    NUMBER) IS
      SELECT TrigEventQueueId, PartitionDate FROM TRIGEVENTQUEUE A
      WHERE TrigEventQueueId IN (SELECT TrigEventQueueId
           FROM (  SELECT teq.TrigEventQueueId, teq.PartitionDate, teq.EventProcessingSessionId,teq.TrigEventStateId
                     FROM svoltp.TrigEventQueue teq,
                          programtrigevent pte,
                          trigevent te
                    WHERE     teq.TrigEventStateId = gcEventStatePending
                          AND teq.EventProcessingSessionId IS NULL
                          AND teq.ProgramTrigEventId = pte.ProgramTrigEventId
                          AND pte.TrigEventId = te.TrigEventId
                          AND teq.PartitionDate >
                                 (SYSDATE - (te.NumHoursUntilEventExpired / 24))
                          AND teq.PartitionDate > vOldestPossibleDate
                 ORDER BY teq.TrigEventCreatedTS) a
          WHERE ROWNUM <= vCount1)
          FOR UPDATE OF A.TrigEventQueueId, A.PARTITIONDATE SKIP LOCKED;
   BEGIN
      vSessionId := TrigEventSessionIdSeq.NEXTVAL;
      vprogramidlst := pprogramidlst;
      SELECT DefaultValue
        INTO vDefaultValue
        FROM svoltp.Param
       WHERE ParamId = gcMaxPenEventsParam;
        SELECT DefaultValue
        INTO vETInterval
        FROM svoltp.Param
       WHERE ParamId = 2755;
      -- Use MAX number of expiry hours to identify an oldest possible date/time that any event could be picked up for.
      SELECT SYSDATE - (MAX (NumHoursUntilEventExpired) / 24)
        INTO vOldestPossibleDate
        FROM trigevent;
         SELECT COUNT(1) INTO VCOUNT1
           FROM (   SELECT teq.TrigEventQueueId, teq.PartitionDate
                     FROM svoltp.TrigEventQueue teq,
                          programtrigevent pte,
                          trigevent te
                    WHERE     teq.TrigEventStateId = gcEventStatePending
                          AND teq.EventProcessingSessionId IS NULL
                          AND teq.ProgramTrigEventId = pte.ProgramTrigEventId
                          AND pte.TrigEventId = te.TrigEventId
                          AND teq.PartitionDate >
                                 (SYSDATE - (te.NumHoursUntilEventExpired / 24))
                          AND teq.PartitionDate > vOldestPossibleDate
                                          ORDER BY teq.TrigEventCreatedTS) a
          WHERE ROWNUM <= vDefaultValue;
     IF VCOUNT1 > 0 THEN
          SELECT count(1) into vcount FROM ETINSTANCESTATUS
                WHERE datediff ('SS', INSTANCEUPDATETIME, SYSDATE) < vETInterval;
                if vcount > 0 then
                vcount1 := round(vcount1/vcount);
            else
            vcount1  := vcount1;
                end if;
          END IF;
       OPEN PRCURSOR(vcount1);
      LOOP
      FETCH PRCURSOR BULK COLLECT INTO vEventRec, vEventPartitionDate LIMIT 100;
--      EXIT WHEN PRCURSOR%NOTFOUND;
      --SVOLTP.PKGSVOLTPLOCK.SLEEP(1);
      FORALL i IN vEventRec.FIRST .. vEventRec.LAST
         UPDATE svoltp.TrigEventQueue teq
            SET teq.EventProcessingSessionId = vSessionId,
                teq.TrigEventStateId = gcEventStateLocked,  --6 : Locked State
                teq.LastUser = 1003,
                teq.LastUpdate = SYSDATE
          WHERE     teq.TrigEventQueueId = vEventRec (i)
                AND teq.PartitionDate = vEventPartitionDate (i);
              END LOOP;
      COMMIT;
      CLOSE PRCURSOR;
      OPEN EventCur FOR
         SELECT TrigEventQueueId, ProgramTrigEventId, PartitionDate
           FROM svoltp.TrigEventQueue teq
          WHERE     teq.EventProcessingSessionId = vSessionId
                AND teq.TrigEventStateId = gcEventStateLocked
                AND teq.PartitionDate > vOldestPossibleDate;
   EXCEPTION
      WHEN OTHERS
      THEN
         OPEN EventCur FOR
            SELECT 1
              FROM DUAL
             WHERE 1 = 2;
   END GetPendingItems;

【问题讨论】:

随着循环 EXIT 被注释掉,它将永远运行...... ... 但是在使用批量收集时,出口需要在循环的末尾,而不是直接在获取之后;也许那是您的问题,有些工作没有完成,而您尝试调试它却变得更糟? (不过,您的 when others 异常处理程序不会帮助您找到任何真正的问题,如果这不是问题的话......) 【参考方案1】:

看起来您已注释掉 exit 作为您尝试调试的一部分 - 这就是导致 Toad 无响应的原因,因为这意味着循环永远不会退出(正如 Tony Andrews 指出的那样)。

有了exit,如果游标查询发现的行数少于100 行,您将不会处理任何内容,即使实际检索到了一些数据,您也会看到PRCURSOR%NOTFOUND。如果它发现超过 100 行,您将处理第一批,但仍然会丢失最后一批少于 100 的记录。

因此您需要将exit 移动到循环的末尾并更改条件:

  LOOP
  FETCH PRCURSOR BULK COLLECT INTO vEventRec, vEventPartitionDate LIMIT 100;
  --SVOLTP.PKGSVOLTPLOCK.SLEEP(1);
  FORALL i IN 1 .. vEventRec.COUNT
     UPDATE svoltp.TrigEventQueue teq
        SET teq.EventProcessingSessionId = vSessionId,
            teq.TrigEventStateId = gcEventStateLocked,  --6 : Locked State
            teq.LastUser = 1003,
            teq.LastUpdate = SYSDATE
      WHERE     teq.TrigEventQueueId = vEventRec (i)
            AND teq.PartitionDate = vEventPartitionDate (i);
  EXIT WHEN vEventRec.COUNT < 100;
  END LOOP;

我还将FORALL 循环更改为使用1..count 而不是first..last,因此如果最后一个实际批次正好是100 行,而下一个批次的行数为零,则不会有问题 - 它'将在FORALL 中什么都不做,然后退出。

this Oracle Magazine article 的后半部分介绍了这种情况(“改掉 %NOTFOUND 习惯”)。

【讨论】:

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

批量收集到特定的收集列

使用“更新”批量收集

立即执行中的批量收集限制

Oracle:批量收集性能

在同一个嵌套表上批量收集两次

为啥打开游标后批量收集加快了抓取速度?