5

オラクルに接続されたWebアプリに取り組んでいます。Oracleには、「アクティブ化された」列を持つテーブルがあります。この列を一度に1に設定できるのは1行だけです。これを強制するために、JavaでSERIALIZED分離レベルを使用していますが、「トランザクションをシリアル化できません」というエラーが発生し、その理由を理解できません。

READCOMMITTEDの分離レベルでうまくいくかどうか疑問に思っていました。だから私の質問はこれです:

次のSQLを含むトランザクションがある場合:

SELECT *
FROM MODEL;

UPDATE MODEL
SET ACTIVATED = 0;

UPDATE MODEL
SET ACTIVATED = 1
WHERE RISK_MODEL_ID = ?;

COMMIT;

これらのトランザクションの複数が同時に実行される可能性があるとすると、複数のMODEL行でアクティブ化されたフラグを1に設定することは可能でしょうか?

どんな助けでもいただければ幸いです。

4

3 に答える 3

3

ソリューションは機能するはずです。最初の更新でテーブル全体がロックされます。別のトランザクションが終了していない場合、更新は待機します。2回目の更新では、テーブルをロックしているため、1つの行だけが値1になることが保証されます(ただし、INSERTステートメントは妨げられません)。

また、存在する行があることを確認する必要がありRISK_MODEL_IDます(または、トランザクションの最後に値が「1」の行がゼロになります)。

同時INSERTステートメントを防ぐには、テーブルをロックします(排他モード)。

于 2009-09-22T22:28:37.360 に答える
3

一意の関数ベースのインデックスを使用して、アクティブ化されたフラグが1に設定された1行のみを持つという制約をOracleに処理させることを検討できます。

CREATE UNIQUE INDEX MODEL_IX ON MODEL ( DECODE(ACTIVATED, 1, 1, NULL));

これは、フラグが1に設定されている複数の行を停止しますが、フラグが1に設定されている行が常に1つあることを意味するわけではありません。

于 2009-09-22T22:33:52.340 に答える
2

一度に1つのトランザクションのみを実行できるようにする必要がある場合は、FOR UPDATE構文を使用できます。ロックが必要な単一の行があるため、これは非常に効率的なアプローチです。

declare
    cursor c is 
        select activated
        from model
        where activated = 1
        for update of activated;
    r c%rowtype;
begin
    open c;
    --  this statement will fail if another transaction is running
    fetch c in r;
    ....
    update model
    set activated = 0
    where current of c;

    update model
    set activated = 1
    where risk_model_id = ?;

    close c;

    commit;
end;
/

commitロックを解放します 。

デフォルトの動作では、行が解放されるまで待機します。それ以外の場合は、を指定できますNOWAIT。その場合、現在アクティブな行を更新しようとする他のセッションはすぐに失敗するかWAIT、ポーリング時間のオプションを追加できます。 NOWAITは、ハングするリスクを完全に回避することを選択するオプションです。また、他の誰かがテーブルを更新していることをユーザーに通知する機会も与えられます。

このアプローチは、テーブル内のすべての行を更新するよりもはるかにスケーラブルです。WWが示したように、関数ベースのインデックスを使用して、ACTIVATED=1を持つことができるのは1行のみであるというルールを適用します。

于 2009-09-23T05:46:32.230 に答える