目標の合計を維持し、既存の値と新しい値の差を返す 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ステートメントもアトミックではなく、同様の問題が発生する可能性があります。
これを防ぐ方法を知っている人はいますか?