2

ストアドプロシージャでFORUPDATE句を使用する場合、いつ「コミット」する必要がありますか?開いたカーソルを閉じた後、または開いたカーソルを閉じる前に?以下は私が使用している手順です、私はそれを正しい方法でやっていますか?

CREATE OR REPLACE PROCEDURE Proc_UpdateCSClientCount(inMerid     IN  VARCHAR2,
                                                     outCliCount  OUT NUMBER,
                                                     outretvalue  OUT NUMBER)
AS
   CURSOR c1 IS
      SELECT CLIENT_COUNT
        FROM OP_TMER_CONF_PARENT
       WHERE MER_ID = inMerid
      FOR UPDATE OF CLIENT_COUNT;
BEGIN
   OPEN c1;
   IF SQL%ROWCOUNT = 1 THEN
      FETCH c1 INTO outCliCount;
      outCliCount := outCliCount + 1;
      UPDATE OP_TMER_CONF_PARENT
         SET CLIENT_COUNT = outCliCount
       WHERE CURRENT OF c1;
   END IF;
   outretvalue := 0;
   CLOSE c1;
   COMMIT;
EXCEPTION
   WHEN no_data_found THEN
      outretvalue := -1;
END;
4

3 に答える 3

6

トランザクションの最後にコミットする必要があります。FOR UPDATEトランザクションの終了がループの途中にある合理的なケースを見つけることができるとは思えません。

頻繁にコミットすることは良いことだと聞いたことがあるかもしれません。これは間違った神話です。これは完全に間違っています。Oracle では逆です。コミットには追加の作業が必要なため、すべての作業が完了してからコミットする必要があります。

さらに、論理的な観点からは、作業の半分を完了するのではなく、最初からやり直すことができれば、エラーからの回復が想像を絶するほど簡単になります。

IMO、手順でコミットすることは非常にまれです。呼び出し元のアプリケーションは、必要なチェックを行い、データをコミットするかどうかを最終的に決定する必要があります。

FOR UPDATE結論として、ループを越えてコミットすることはできません(それは を生成しますORA-01002: fetch out of sequence)。これは良いことです。通常のループを越えてコミットしていることに気付いたときはいつでも、コミットが本当に必要かどうかを自問する必要があります。ほとんどの場合、そうではありません。

本当にコミットする必要があり一度だけフェッチする場合は、カーソルを閉じる前にコミットするか後にコミットするかは問題ではありません。


コードの抜粋に従って更新してください。コードには修正が必要なことがたくさんあります(直接の製品コードではないと思いますが、それでも):

  • 例外が発生することはありません。暗黙的にのみSELECT INTO生成できNO_DATA_FOUNDます。
  • SQL%ROWCOUNT前のステートメントが の場合、NULL ですSELECT
  • を使用できますc1%ROWCOUNTが、これはフェッチされた行数のみを返します:0最初のopen.
  • 私は主にFOR UPDATE NOWAIT、2 つのセッションが互いにブロックされないように使用します。のみを使用する場合は、事前に使用せずFOR UPDATEに単一で使用することもできます。UPDATESELECT
  • これは好みの問題ですが、リターン コードはエラーが発生しやすく、一般的には例外が優先されます。エラーを伝播させます。idなぜ誰かが存在しないでこの関数を呼び出すのでしょうか? これはおそらく呼び出し元のアプリ/手順のバグであるため、キャッチしないでください。

したがって、次のように手順を書き直すことができます。

CREATE OR REPLACE PROCEDURE Proc_UpdateCSClientCount(inMerid     IN  VARCHAR2, 
                                                     outCliCount OUT NUMBER) AS
BEGIN
   -- lock the row, an exception will be raised if this row is locked
   SELECT CLIENT_COUNT + 1
     INTO outCliCount
     FROM OP_TMER_CONF_PARENT
    WHERE MER_ID = inMerid
   FOR UPDATE OF CLIENT_COUNT NOWAIT;
   -- update the row
   UPDATE OP_TMER_CONF_PARENT
      SET CLIENT_COUNT = CLIENT_COUNT + 1
    WHERE MER_ID = inMerid;
END;
于 2012-11-07T09:24:21.907 に答える
2

Oracle ドキュメントから:

すべての行は、フェッチされるときではなく、カーソルを開くときにロックされます。 トランザクションをコミットまたはロールバックすると、行はロック解除されます。 行はロックされていないため、コミット後に FOR UPDATE カーソルからフェッチすることはできません。

それは重要です。カーソルを閉じる前または後にコミットする場合、タスク(フェッチの完了)を完了したかどうかは重要ではありません。

ただし、フェッチ間のコミットが必要な場合は、回避策として、更新ではなく行 ID を使用してくださいwhere current of。ドキュメントの例:

DECLARE
   CURSOR c1 IS SELECT last_name, job_id, rowid FROM employees;
   my_lastname   employees.last_name%TYPE;
   my_jobid      employees.job_id%TYPE;
   my_rowid      UROWID;
BEGIN
   OPEN c1;
   LOOP
      FETCH c1 INTO my_lastname, my_jobid, my_rowid;
      EXIT WHEN c1%NOTFOUND;
      UPDATE employees SET salary = salary * 1.02 WHERE rowid = my_rowid;
      -- this mimics WHERE CURRENT OF c1
      COMMIT;
   END LOOP;
   CLOSE c1;
END;
/

UPDATE(質問の編集後):カーソルなしで、単一のSQLでそれを行うことができます。

UPDATE OP_TMER_CONF_PARENT 
set CLIENT_COUNT = CLIENT_COUNT +1 
where MER_ID = inMerid;

UPDATE2。動作させるには、コードを次のように更新する必要があります。

...
open C1;
FETCH C1 into OUTCLICOUNT;
--dbms_output.put_line(' count:'||c1%rowcount);
IF c1%rowcount = 1 THEN
      outCliCount := outCliCount + 1;
...

つまり、影響を受ける行をカウントする前にフェッチを実行する必要があり、影響を受ける行はc1%rowcountではなくsql%rowcountです。行が更新されたかどうかを知りたい場合は、else を if に置き、outretvalue パラメーターに特別な値を割り当てる必要があります。

于 2012-11-07T08:35:47.600 に答える
0

カーソルを閉じる前にコミットし、その後再度フェッチしようとすると、INVALID_CURSOR 例外が発生します。カーソルを閉じた後にコミットすることをお勧めします。

于 2012-11-07T08:33:03.550 に答える