アリスとボブの両方が商品リストを編集しているeコマースサイトを考えてみましょう。ボブが価格を更新している間、アリスは説明を改善しています。彼らは同時にAcme Wonder Widgetの編集を開始します。ボブは最初に終了し、新しい価格で製品を保存します。アリスは説明を更新するのに少し時間がかかり、終了すると、新しい説明とともに製品を保存します。残念ながら、彼女はまた、意図しない古い価格で価格を上書きします。
私の経験では、これらの問題はWebアプリでは非常に一般的です。一部のソフトウェア(wikiソフトウェアなど)にはこれに対する保護機能があります。通常、2番目の保存は「編集中にページが更新された」ために失敗します。しかし、ほとんどのWebサイトにはこの保護機能がありません。
コントローラーメソッド自体がスレッドセーフであることは注目に値します。通常、データベーストランザクションを使用します。これにより、アリスとボブがまったく同じタイミングで保存を試みても、破損が発生しないという意味で安全です。競合状態は、アリスまたはボブのブラウザに古いデータがあるために発生します。
どうすればこのような競合状態を防ぐことができますか?特に、知りたいのですが。
「書き込みを読み取る」必要があります。つまり、変更を書き留める前に、レコードを再度読み取り、最後に読み取ってから変更が加えられていないかどうかを確認する必要があります。これは、フィールドごと(細粒度)またはタイムスタンプに基づいて(粗粒度)行うことができます。このチェックを行う間、レコードに排他ロックが必要です。変更が行われなかった場合は、変更を書き留めてロックを解除できます。その間にレコードが変更された場合は、トランザクションを中止し、ロックを解除してユーザーに通知します。
私は2つの主な方法を見てきました:
非表示の入力で使用が編集しているページの最後の更新のタイムスタンプを追加します。タイムスタンプをコミットすると、現在のタイムスタンプがチェックされ、一致しない場合は他の誰かによって更新されてエラーが返されます。
プロ:複数のユーザーがページの異なる部分を編集できます。エラーページは、2番目のユーザーが自分の変更を新しいページにマージできる差分ページにつながる可能性があります。
短所:大規模な同時編集中に、作業の大部分が無駄になることがあります。
ユーザーがページの編集を開始すると、それを妥当な時間ロックします。その後、別のユーザーが編集しようとすると、エラーページが表示され、ロックが期限切れになるか、最初のユーザーがコミットするまで待機する必要があります。
長所:編集作業が無駄になりません。
con:悪意のあるユーザーがページを無期限にロックする可能性があります。期限切れのロックを持つページは、(テクニック1を使用して)別の方法で対処しない限り、コミットできる可能性があります。
楽観的同時実行制御 を使用します。
問題のテーブルにversionNumberまたはversionTimestamp列を追加します(整数が最も安全です)。
ユーザー1はレコードを読み取ります。
{id:1, status:unimportant, version:5}
ユーザー2はレコードを読み取ります。
{id:1, status:unimportant, version:5}
ユーザー1はレコードを保存します。これによりバージョンが増加します。
save {id:1, status:important, version:5}
new value {id:1, status:important, version:6}
ユーザー2は、読み取ったレコードを保存しようとします。
save {id:1, status:unimportant, version:5}
ERROR
Hibernate/JPAは@Version
アノテーションでこれを自動的に行うことができます
読み取りレコードの状態を、通常はセッション中のどこかに維持する必要があります(これは非表示のフォーム変数よりも安全です)。
一部のオブジェクトリレーショナルマッピング(ORM)システムは、データベースから読み込まれてからオブジェクトのどのフィールドが変更されたかを検出し、それらの値のみを設定するSQL更新ステートメントを構築します。 Ruby on RailsのActiveRecordは、そのようなORMの1つです。
正味の影響は、ユーザーが変更しなかったフィールドが、データベースに送信されるUPDATEコマンドに含まれないことです。異なるフィールドを同時に更新する人は、お互いの変更を上書きしません。
使用しているプログラミング言語に応じて、使用可能なORMを調査し、それらのいずれかがアプリケーションで「ダーティ」とマークされたデータベースの列のみを更新するかどうかを確認します。