Oracle简单更新转换为CURSOR FOR UPDATE OF

时间:2012-01-16 22:56:13

标签: sql oracle plsql locking

我在Oracle RDBMS中工作。

假设我在存储过程的上下文中有以下UPDATE语句:

UPDATE memberPlan mp /* C$MP$MEMBERPLANID */
SET ( mp.lastContractId          ,
      mp.lastContractIdChanged   ,
      mp.lastGroupOrPolicyNumber ) = (
         SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
            htu.contractId                        as lastContractId          ,
            CASE WHEN htu.contractId IS NULL THEN 
               NULL                               
            ELSE                                  
               varRunDate                         
            END                                   as lastContractIdChanged   ,
            htu.groupOrPolicyNumber               as lastGroupOrPolicyNumber
         FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
         WHERE htu.memberPlanId = mp.memberPlanId)
WHERE EXISTS (
    SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */ 1
    FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
    WHERE htu.memberPlanId = mp.memberPlanId);

问题是memberplan表被锁定了 程序正在运行。

我想一次只排行锁定,更新和释放一行 并将表中的其余行保持解锁状态。

所以我设计了以下解决方案:

DECLARE
   CURSOR memberPlan1_cur(parmRunDate IN DATE) IS
      SELECT 
         mp.lastContractId           as lastContractId             ,
         mp.lastContractIdChanged    as lastContractIdChanged      ,
         mp.lastGroupOrPolicyNumber  as lastGroupOrPolicyNumber    ,
         htu.lastContractId          as updLastContractId          ,
         htu.lastContractIdChanged   as updLastContractIdChanged   ,
         htu.lastGroupOrPolicyNumber as updLastGroupOrPolicyNumber 
      FROM 
         memberPlan mp
            INNER JOIN
              (SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
                  memberPlanId                      as memberPlanId            ,
                  contractId                        as lastContractId          ,
                  CASE WHEN contractId IS NULL THEN 
                     NULL                           
                  ELSE                              
                     parmRunDate
                  END                               as lastContractIdChanged   ,
                  groupOrPolicyNumber               as lastGroupOrPolicyNumber
               FROM htUpdateMemberPlan) htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
            ON mp.memberPlanId = htu.memberPlanId
      WHERE EXISTS (
         SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */ 1
         FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
         WHERE htu.memberPlanId = mp.memberPlanId)
      FOR UPDATE OF
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber ;
BEGIN
   FOR memberPlan1_row IN memberPlan1_cur(varRunDate) LOOP
      UPDATE memberPlan mp /* C$MP$MEMBERPLANID */
      SET mp.lastContractId          = memberPlan1_row.updLastContractId          ,
          mp.lastContractIdChanged   = memberPlan1_row.updLastContractIdChanged   ,
          mp.lastGroupOrPolicyNumber = memberPlan1_row.updLastGroupOrPolicyNumber 
      WHERE CURRENT OF memberPlan1_cur;
   END LOOP;
   COMMIT;
END;

我基本上都会返回更新值 我要更新的源字段。

上述解决方案可针对以下代码进行优化,
上面SQL的一个变体,其中存在测试是
取而代之的是直接INNER JOIN:

DECLARE
   CURSOR memberPlan1_cur(parmRunDate IN DATE) IS
      SELECT /*+ FULL(mp) INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
         mp.lastContractId                     as lastContractId             ,
         mp.lastContractIdChanged              as lastContractIdChanged      ,
         mp.lastGroupOrPolicyNumber            as lastGroupOrPolicyNumber    ,
         htu.contractId                        as updLastContractId          ,
         CASE WHEN htu.contractId IS NULL THEN 
            NULL                           
         ELSE                              
            parmRunDate
         END                                   as updLastContractIdChanged   ,
         htu.groupOrPolicyNumber               as updLastGroupOrPolicyNumber 
      FROM 
         memberPlan mp
            INNER JOIN htUpdateMemberPlan htu  /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
            ON mp.memberPlanId = htu.memberPlanId
      FOR UPDATE OF
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber ;
BEGIN
   FOR memberPlan1_row IN memberPlan1_cur(varRunDate) LOOP
      UPDATE memberPlan mp /* C$MP$MEMBERPLANID */
      SET mp.lastContractId          = memberPlan1_row.updLastContractId          ,
          mp.lastContractIdChanged   = memberPlan1_row.updLastContractIdChanged   ,
          mp.lastGroupOrPolicyNumber = memberPlan1_row.updLastGroupOrPolicyNumber 
      WHERE CURRENT OF memberPlan1_cur;
   END LOOP;
   COMMIT;
END;

我考虑过以下另一种方式:

DECLARE
   CURSOR memberPlan1_cur IS
      SELECT 
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber 
      FROM memberPlan mp
      WHERE EXISTS (
         SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */ 1
         FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
         WHERE htu.memberPlanId = mp.memberPlanId)
      FOR UPDATE OF
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber ;
BEGIN
   FOR memberPlan1_row IN memberPlan1_cur LOOP
      UPDATE memberPlan mp
      SET (mp.lastContractId          ,
           mp.lastContractIdChanged   ,
           mp.lastGroupOrPolicyNumber ) =
              (SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
                  htu.contractId                        as lastContractId          ,
                  CASE WHEN htu.contractId IS NULL THEN 
                     NULL                           
                  ELSE                              
                     varRunDate
                  END                                   as lastContractIdChanged   ,
                  htu.groupOrPolicyNumber               as lastGroupOrPolicyNumber
               FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
               WHERE mp.memberPlanId = htu.memberPlanId)
      WHERE CURRENT OF memberPlan1_cur;
   END LOOP;
   COMMIT;
END;

上面的解决方案我相信会给我带来问题因为 更新不是单独分配值,如

SET column1=value1,column2=value2,column3=value3

但使用

SET (column1,column2,column3) = (SELECT ...) syntax

我想到了使用第二种方法实现目标的第三种方式 光标和3个变量:

DECLARE
   varLastContractId          memberPlan.lastContractId%TYPE          ;
   varLastContractIdChanged   memberPlan.lastContractIdChanged%TYPE   ;
   varLastGroupOrPolicyNumber memberPlan.lastGroupOrPolicyNumber%TYPE ;

   CURSOR memberPlan1_cur IS
      SELECT 
         mp.memberPlanId            ,
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber 
      FROM memberPlan mp
      WHERE EXISTS (
         SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */ 1
         FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
         WHERE htu.memberPlanId = mp.memberPlanId)
      FOR UPDATE OF
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber ;

   CURSOR htUpdateMemberPlan1_cur(
             parmRunDate      IN DATE  ,
             parmMemberPlanId IN NUMBER) IS
      SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
         htu.contractId                        as lastContractId          ,
         CASE WHEN htu.contractId IS NULL THEN 
            NULL                           
         ELSE                              
            parmRunDate
         END                                   as lastContractIdChanged   ,
         htu.groupOrPolicyNumber               as lastGroupOrPolicyNumber
      FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
      WHERE htu.memberPlanId = parmMemberPlanId;
BEGIN
   FOR memberPlan1_row IN memberPlan1_cur LOOP
      OPEN htUpdateMemberPlan1_cur(
         varRunDate                   ,
         memberPlan1_row.memberPlanId );
      FETCH htUpdateMemberPlan1_cur INTO 
         varLastContractId          ,
         varLastContractIdChanged   ,
         varLastGroupOrPolicyNumber ;

      UPDATE memberPlan mp
      SET mp.lastContractId          = varLastContractId          ,
          mp.lastContractIdChanged   = varLastContractIdChanged   ,
          mp.lastGroupOrPolicyNumber = varLastGroupOrPolicyNumber 
      WHERE CURRENT OF memberPlan1_cur;

      CLOSE htUpdateMemberPlan1_cur;
   END LOOP;
   COMMIT;
END;

上述三种CURSOR解决方案中的哪一种是正确的解决方法?

1 个答案:

答案 0 :(得分:0)

关闭袖口我会猜测第一个游标版本的性能会比第二个游标版本好(假设它们的结果相同)。推理:使用htUpdateMemberPlan执行单个内部联接(第一个游标版本)将比第二个版本的多个联接(游标循环中的每次迭代一次)执行得更好。但这真是一个猜测。我会运行一个sql跟踪并查看发生了什么。

似乎第一个版本从htUpdateMemberPlan中检索值的静态快照,该快照一直在游标循环中使用。第二个版本在处理每一行时对htUpdateMemberPlan进行查找。因此,有可能的是,varRunDate的值可能会在创建游标的时间与第二个版本中处理游标的特定行之间的另一个会话中更改。但不是在第一个版本中。这对两种方法的意义略有不同。你想要哪个?再次,只是挥手在这里。可能是错的。

还想知道CASE语句是否可能被NVL2(contractId,varRunDate,null)替换。然后,也许再好不过了:)