開発者の観点から答えを提供します。
私の意見では、説明したような行の競合が発生するのは、アプリケーションにバグがあるためです。ほとんどの場合、このタイプの競合は、更新が失われる脆弱性の兆候です。AskTomのこのスレッドは、失われた更新の概念を説明しています。
更新が失われるのは次の場合です。
セッション1:トムの従業員レコードを読み取る
セッション2:トムの従業員レコードを読み取る
セッション1:トムの従業員レコードを更新する
セッション2:トムの従業員レコードを更新する
セッション2は、セッション1の変更を目にすることなく上書きするため、更新が失われます。
更新が失われるという厄介な副作用が1つあります。セッション1がまだコミットされていないため、セッション2がブロックされる可能性があります。ただし、主な問題は、セッション2が盲目的にレコードを更新することです。両方のセッションが次のステートメントを発行するとします。
UPDATE table SET col1=:col1, ..., coln=:coln WHERE id = :pk
両方のステートメントの後、セッション1の変更は上書きされました。セッション2は、行がセッション1によって変更されたことを通知されていません。
更新の喪失(および競合の副作用)は決して発生しないはずです。100%回避可能です。ロックを使用して、2つの主要な方法でそれらを防止する必要があります。楽観的ロックと悲観的ロックの。
1)悲観的ロック
行を更新します。このモードでは、その行のロックをリクエストすることで、他の人がこの行を変更できないようにします(SELECT ... FOR UPDATE NOWAITステートメント)の。行がすでに変更されている場合は、エラーメッセージが表示されます。これをエンドユーザーに適切に変換できます(この行は別のユーザーによって変更されています)。行が使用可能な場合は、変更(更新)を行い、トランザクションが完了するたびにコミットします。
2)楽観的ロック
行を更新します。ただし、おそらく複数のトランザクションを使用して行を更新するため(Webベースのステートレスアプリケーション)、またはユーザーがロックを長時間保持しないようにするために、その行のロックを維持する必要はありません(他のユーザーがブロックされる可能性があります)。その場合、すぐにロックをリクエストすることはありません。マーカーを使用して、更新の発行時に行が変更されていないことを確認します。すべての列の値をキャッシュするか、自動的に更新されるタイムスタンプ列、またはシーケンスベースの列を使用できます。どのような選択を行っても、更新を実行しようとするときに、次のようなクエリを発行して、その行のマーカーが変更されていないことを確認します。
SELECT <...>
FROM table
WHERE id = :id
AND marker = :marker
FOR UPDATE NOWAIT
クエリが行を返す場合、更新を行います。そうでない場合、これは最後にクエリを行ってから誰かが行を変更したことを意味します。プロセスを最初から再起動する必要があります。
注:DBにアクセスするすべてのアプリケーションに対する完全な信頼がある場合は、楽観的ロックの直接更新に依存できます。直接発行できます:
UPDATE table
SET <...>,
marker = marker + 1
WHERE id = :id;
ステートメントが行を更新しない場合、誰かがこの行を変更したことを知っているため、最初からやり直す必要があります。
すべてのアプリケーションがこのスキームに同意すれば、他の誰かにブロックされることはなく、ブラインド更新を回避できます。ただし、事前に行をロックしないと、別のアプリケーション、バッチジョブ、または直接更新が楽観的ロックを実装していない場合、無期限にロックされやすくなります。これが、ロックスキームの選択に関係なく、常に行をロックすることをお勧めする理由です(行をロックすると、rowidを含むすべての値を取得するため、パフォーマンスヒットは無視できます)。
TL; DR
- 事前にロックを持たずに行を更新すると、アプリケーションが潜在的な「フリーズ」にさらされます。DBへのすべてのDMLが楽観的または悲観的ロックを実装している場合、これは回避できます。
- SELECTステートメントが以前のSELECTと一致する値を返すことを確認します(更新の損失の問題を回避するため)