4

目標の合計を維持し、既存の値と新しい値の差を返す Oracle データベースで更新/挿入を実行する PL/SQL 関数があります。
これが私がこれまでに持っているコードです:

FUNCTION calcTargetTotal(accountId varchar2, newTotal numeric ) RETURN number is  
oldTotal numeric(20,6);  
difference numeric(20,6);  

begin
    difference := 0;  
    begin  
        select value into oldTotal
        from target_total
        WHERE account_id = accountId
        for update of value;

        if (oldTotal != newTotal) then
            update target_total
            set value = newTotal
            WHERE account_id = accountId
            difference := newTotal - oldTotal;
        end if;
    exception
        when NO_DATA_FOUND then
        begin
            difference := newTotal;
            insert into target_total
                ( account_id, value )
            values
                ( accountId, newTotal );

        -- sometimes a race condition occurs and this stmt fails
        -- in those cases try to update again
        exception
            when DUP_VAL_ON_INDEX then
            begin
                difference := 0;
                select value into oldTotal
                from target_total
                WHERE account_id = accountId
                for update of value;

                if (oldTotal != newTotal) then
                    update target_total
                    set value = newTotal
                    WHERE account_id = accountId
                    difference := newTotal - oldTotal;
                end if;
            end;
        end;
    end;
    return difference
end calcTargetTotal;

これは、単体テストでは期待どおりに機能し、複数のスレッドが失敗することはありません。
ただし、ライブ システムにロードすると、次のようなスタック トレースで失敗することがわかりました。

ORA-01403: no data found  
ORA-00001: unique constraint () violated  
ORA-01403: no data found  

行番号 (コンテキスト外では無意味なので削除しました) は、最初の更新がデータがないために失敗し、挿入が一意性のために失敗し、2 番目の更新がデータなしで失敗していることを確認します。これは不可能なはずです。

私が他のスレッドで読んだことから、MERGEステートメントもアトミックではなく、同様の問題が発生する可能性があります。

これを防ぐ方法を知っている人はいますか?

4

2 に答える 2

1

オラクルが言っているように、あなたが直面している状況は不可能ではありません。挿入しようとしているがまだコミットされていないキーを別のプロセスが挿入した場合、説明されている動作を得ることができます。更新では挿入されたレコードは表示されませんが、挿入された行がまだコミットされていない場合でも、一意のインデックスに重複する値を追加する試みは禁止されています。

頭に浮かぶ唯一の解決策は、コミットされていない挿入がこのテーブルにぶら下がっている時間を最小限に抑えるか、ある種のロックスキームを実装するか、挿入が失敗したときに他のトランザクションが完了するのを待つことです。

于 2011-03-08T18:40:25.907 に答える