2

コントラクトというテーブルがあります。これらの契約記録は、外部サイトのユーザーによって作成され、内部サイトのスタッフによって承認または拒否される必要があります。コントラクトが拒否されると、データベースから削除されます。ただし、受け入れられると、契約受け入れと呼ばれる新しいレコードが生成されます。このレコードは、独自のテーブルに書き込まれ、契約に存在するデータから派生します。

問題は、2人の社内スタッフがそれぞれ同じ契約を結ぶ可能性があることです。最初のユーザーが承諾し、契約承諾レコードが生成されます。次に、同じ契約レコードがページ上で開いたままの状態で、2番目のユーザーが再び契約を受け入れ、重複した受け入れレコードを作成します。

これを乗り越えるための迅速で汚い方法は、受け入れられる直前にデータベースからコントラクトを取得し、ステータスを確認して、すでに受け入れられたことを示すエラーメッセージを生成することです。これはおそらくほとんどの状況で機能しますが、ユーザーはそれでもまったく同時に[同意する]ボタンをクリックして、この検証コードを忍び込むことができます。

また、2つのスレッドが同時に同じコード領域に入るのを防ぐ、データレイヤーの奥深くにあるスレッドロックについても検討しましたが、アプリは2つの負荷分散サーバーに存在するため、ユーザーは別々のサーバーにいる可能性があります。このアプローチを役に立たなくします。

私が考えることができる唯一の方法は、データベースに存在する必要があります。概念的には、ストアドプロシージャまたはテーブルをロックして、同時に2回更新できないようにしたいのですが、ここではOracleについて十分に理解していない可能性があります。アップデートはどのように機能しますか?更新要求がまったく同時に発生しないように、どういうわけかキューに入れられていますか?その場合、SQLでレコードのステータスを確認し、すでに受け入れられていることを示す値をoutパラメーターに返すことができます。ただし、更新要求がキューに入れられていない場合でも、2人がまったく同時に更新SQLにアクセスする可能性があります。

これについて行く方法についての良い提案を探しています。

4

2 に答える 2

2

まず、契約ごとに1つの契約承諾しか存在できない場合、契約承諾には、独自のプライマリ(または一意の)キーとして契約IDを含める必要があります。これにより、重複が不可能になります。

次に、最初のユーザーが契約を受け入れている間に2番目のユーザーが契約を受け入れようとするのを防ぐために、受け入れプロセスで契約行をロックすることができます。

select ...
from Contract
where contract_id = :the_contract
for update nowait;

insert into Contract_Acceptance ...

2番目のユーザーが受け入れようとすると、例外が発生して失敗します。

ORA-00054: resource busy and acquire with nowait specified
于 2011-08-11T13:36:01.777 に答える
1

一般に、問題には2つのアプローチがあります

オプション1:悲観的なロック

このシナリオでは、悲観的であるため、選択したときにテーブルの行をロックします。ユーザーがテーブルにクエリを実行すると、次のContractsような操作が行われます。

SELECT *
  FROM contracts
 WHERE contract_id = <<some contract ID>>
   FOR UPDATE NOWAIT;

最初にレコードを選択した人は誰でもそれをロックします。2番目にレコードを選択すると、ORA-00054エラーが発生し、アプリケーションがそれをキャッチして、別のユーザーがすでにレコードをロックしていることを通知します。最初のユーザーが作業を完了すると、Contract_AcceptanceテーブルにINSERTを発行し、トランザクションをコミットします。これにより、テーブルの行のロックが解除されContractsます。

オプション2:楽観的ロック

このシナリオでは、2人のユーザーが競合しないことを楽観視しているため、最初はレコードをロックしません。代わりに、必要なデータとLast_Updated_Timestamp、テーブルに追加する列がまだ存在しない場合はそれを選択します。何かのようなもの

SELECT <<list of columns>>, Last_Updated_Timestamp
  FROM Contracts
 WHERE contract_id = <<some contract ID>>

ユーザーが契約に同意すると、INSERTintoを実行する前に、 onContractsContract_Acceptanceを発行します。UPDATE

UPDATE Contracts
   SET last_updated_timestamp = systimestamp
 WHERE contract_id = <<some contract ID>>
   AND last_update_timestamp = <<timestamp from the initial SELECT>>;

この更新を最初に行った人は成功します(ステートメントは1行を更新します)。これを行う2番目の人は0行を更新します。アプリケーションは、更新によって行が変更されなかったという事実を検出し、他の誰かがすでに行を処理したことを2番目のユーザーに通知します。

どちらの場合にも

いずれの場合もUNIQUE、テーブルに制約を追加することをお勧めしContract_Acceptanceます。Contract_Acceptanceこれにより、特定のに対してテーブルに1つの行のみが存在するようになりますContract_ID

ALTER TABLE Contract_Acceptance
  ADD CONSTRAINT unique_contract_id UNIQUE (Contract_ID)

これは、決して必要とされるべきではないが、アプリケーションがそのロジックを正しく実装しない場合にあなたを保護する2番目の防衛線です。

于 2011-08-11T13:40:09.343 に答える