楽観的ロックと悲観的ロック


572

楽観的ロックと悲観的ロックの違いを理解しています。どちらを一般的に使用するかを誰かに説明してもらえますか?

そして、この質問に対する答えは、クエリを実行するためにストアドプロシージャを使用しているかどうかによって異なりますか?

しかし、チェックするだけで、楽観的とは「読み取り中にテーブルをロックしない」ことを意味し、悲観的とは「読み取り中にテーブルをロックする」ことを意味します。



1
シリアライザビリティでは私が読んだので、それは特に良い質問At any technique type conflicts should be detected and considered, with similar overhead for both materialized and non-materialized conflictsです。
リトルエイリアン

1
ここで、ここSOで、楽観的ロックの根本的な概念についての良い説明を見つけることができます。
Diego Mazzaro

回答:


812

オプティミスティックロックは、レコードを読み取り、バージョン番号(日付、タイムスタンプ、またはチェックサム/ハッシュを含む他の方法)をメモし、レコードを書き戻す前にバージョンが変更されていないことを確認する戦略です。レコードを書き戻すときは、バージョンの更新をフィルタリングして、アトミックであることを確認します。(つまり、バージョンを確認してからレコードをディスクに書き込むまでの間に更新されていません)、1回のヒットでバージョンを更新します。

レコードがダーティ(つまり、バージョンが異なる)の場合、トランザクションを中止し、ユーザーはそれを再開できます。

この戦略は、セッションのためにデータベースへの接続を必ずしも維持する必要がない、大量システムおよび3層アーキテクチャーに最も適しています。この状況では、接続はプールから取得されるため、クライアントは実際にはデータベースロックを維持できず、アクセスごとに同じ接続を使用していない可能性があります。

悲観的ロックとは、レコードが完了するまで、レコードを排他的に使用するためにロックすることです。楽観的ロックよりも完全性がはるかに優れていますが、アプリケーションの設計でデッドロックを回避するように注意する必要があります。悲観的ロックを使用するには、データベースへの直接接続(通常、2層クライアントサーバーアプリケーションの場合)か、接続とは無関係に使用できる外部で利用可能なトランザクションIDが必要です。

後者の場合、TxIDでトランザクションを開き、そのIDを使用して再接続します。DBMSはロックを維持し、TxIDを通じてセッションをバックアップすることができます。これは、2フェーズコミットプロトコル(XACOM +トランザクションなど)を使用した分散トランザクションが機能する方法です。


148
楽観的ロックでは、必ずしもバージョン番号を使用する必要はありません。その他の戦略には、(a)タイムスタンプまたは(b)行自体の状態全体の使用が含まれます。後者の方法は醜いですが、スキーマを変更できない場合に、専用のバージョン列が不要になります。
Andrew Swan

2
@geek-XAなどの分散トランザクションプロトコルにより、個別のトランザクション識別子を1つまたは複数のシステムの周囲に網状化できます。このタイプのプロトコルでは、トランザクション識別子がセッションから切り離され、明示的に提供されるため、プールされた接続を介してロックを使用できます。ただし、これはある程度のオーバーヘッドを招き、アプリケーションがそれらを追跡することについて細心の注意を払っていなければ、ロックとトランザクション識別子をリークする傾向があります。それははるかに重いソリューションです。
ConcernedOfTunbridgeWells

22
@supercat-楽観的ロックは100%未満の正確さであることに同意しないでください-期間中変更されないままである必要があるトランザクションのすべての入力レコードをチェックする限り、それらの悲観的ロック(更新スタイルを選択)と同じくらい正確です同じレコード。主な違いは、楽観的ロックでは競合が発生した場合にのみオーバーヘッドが発生するのに対し、悲観的ロックでは競合のオーバーヘッドが減少することです。したがって、ほとんどのトランザクションが競合しない場合は、楽観的が最適です。通常、ほとんどのアプリでこれが当てはまると思います。
RichVel 14

2
@Legends-オプティミスティックロックを使用することは、Webアプリケーションにとって確かに適切な戦略です。
ConcernedOfTunbridgeWells 2015

2
選択は、読み取りと書き込みの比率にも依存することを言及する必要があります。アプリケーションが主に多くのユーザーによる読み取り専用アプリケーションであり、データを書き込む場合は、楽観的ロックよりも適切です。たとえば、StackOverflowには多くの人がページを読んでいて、時には誰かがページを編集しています。ペシミスティックロックでは、誰がロックを取得しますか?最初の1つ?楽観的ロックでは、ページを編集する人は、ページの最新バージョンを持っている限り、それを行うことができます。
jehon

177

多くの衝突が予想されない場合は、楽観的ロックが使用されます。通常の操作を行う方がコストは低くなりますが、衝突が発生した場合、トランザクションが中止されるため、それを解決するためにより高い価格を支払うことになります。

衝突が予想される場合、悲観的ロックが使用されます。同期に違反するトランザクションは単にブロックされます。

適切なロックメカニズムを選択するには、読み取りと書き込みの量を見積もり、それに応じて計画する必要があります。


通常の場合、ステートメントは完璧ですが、@ skaffmanが回答で述べたように、CAS操作を管理して不正確を許容できる特殊なケースでは、それは実際に依存すると言えます。
Hearen

75

楽観主義は、あなたがそれを読んでいる間、何も変化しないと想定しています。

悲観論は、何かがそれをロックすることを前提としています。

データが完全に読み取られることが不可欠ではない場合は、楽観的に使用してください。奇妙な「ダーティ」な読み取りが行われる可能性がありますが、デッドロックなどが発生する可能性ははるかに低くなります。

ほとんどのWebアプリケーションはダーティリードで問題ありません。まれに、データが次のリロードで正確に集計されないことがあります。

正確なデータ操作(多くの金融取引など)には、悲観論を使用します。表示されていない変更がなく、データが正確に読み取られることが重要です。追加のロックオーバーヘッドはその価値があります。

ああ、Microsoft SQLサーバーのデフォルトはページロックです。基本的には、あなたが読んでいる行と、その両側にいくつかあります。行ロックはより正確ですが、はるかに遅くなります。読み取り中のデッドロックを回避するために、トランザクションを読み取りコミットまたは非ロックに設定することはしばしば価値があります。


JPAオプティミスティックロックを使用すると、読み取りの一貫性を保証できます。
ギリ

4
読み取りの一貫性は別の問題です。PostgreSQL、Oracle、およびその他の多くのデータベースでは、まだコミットされていない更新に関係なく、データの一貫したビューが得られ、排他的な行ロックの影響も受けません。
RichVel 2014

@RichVelに同意する必要があります。一方では、トランザクションの分離レベルがREAD UNCOMMITTEDの場合、ペシミスティックロックがダーティリードをどのように防ぐことができるかがわかります。しかし、ほとんどのデータベース(MS SQL Serverを含む)のデフォルトの分離レベルは「READ COMMITTED」であり、ダーティリードを防止し、オプティミスティックロックを正確に行うことは言うまでもなく、オプティミスティックロックはダーティリードの影響を受けやすいと誤解しています。悲観的。
アンチノーム

Eric Brower氏は、銀行家は他の人とは異なり、汚い操作を好むと述べています。あなたの教祖はトロリーから完全に外れているようです。
リトルエイリアン

1
エリックブリューワーは、CAPの定理でバンキングの一貫性について述べた第一人者です。それはあなたがそれを尊重するものと反対です。
リトルエイリアン

50

すでに言われたことに加えて:

  • optimisticロックは、予測可能性を犠牲にして並行性を向上させる傾向があることを述べておく必要があります。
  • Pessimisticロックは並行性を低下させる傾向がありますが、より予測可能です。あなたはあなたのお金などを払います...

3
予測可能性(どのように定義した場合でも)が悲観的ロックによってどのように改善されるかはわかりません。「ロックが取得されるとトランザクションが完了する」ことを意味する場合は正しいですが、トランザクションに必要なすべてのロックがあるまで、取得に遅延が生じる可能性があります。残りのロック、実際にはDBのデッドロック検出+解決ロジックのために中止される可能性があります。悲観的ロックを使用するアプリの実行時間は非常に予測不可能になる可能性があります-古典的な例は、誰かがレコードXをロックしてから昼食に行き、その後ユーザーがレコードXとYをロックし、次に別のYとZをロックするなどです。 ..
RichVel

40

競合に対処する場合、2つのオプションがあります。

  • 競合を回避するように試みることができ、それが悲観的ロックが行うことです。
  • または、競合の発生を許可することもできますが、トランザクションをコミットするときに競合を検出する必要があります。これが楽観的ロックの機能です。

ここで、次のLost Updateの異常を考えてみましょう。

失われた更新

失われた更新の異常は、Read Committed分離レベルで発生する可能性があります。

上の図では、アリスが40を引き出すことができると信じているaccountが、ボブが口座残高を変更したことに気づかず、この口座には20しか残っていないことがわかります。

悲観的ロック

悲観的ロックは、アカウントで共有ロックまたは読み取りロックを取得することでこの目標を達成し、ボブがアカウントを変更できないようにします。

失われた更新の悲観的ロック

上の図では、アリスとボブの両方が読み取りロックを取得します account両方が、両方のユーザーが読み取っテーブル行のます。Repeatable ReadまたはSerializableを使用すると、データベースはSQL Serverでこれらのロックを取得します。

AliceとBobの両方accountがのPK値でを読み取ったため、11人のユーザーが読み取りロックを解放するまで、どちらもPK値を変更できません。これは、書き込み操作には書き込み/排他ロックの取得が必要であり、共有/読み取りロックは書き込み/排他ロックを防止するためです。

アリスがトランザクションをコミットし、account行の読み取りロックが解放された後でのみ、ボブUPDATEは再開して変更を適用します。アリスが読み取りロックを解放するまで、ボブのUPDATEはブロックされます。

データアクセスフレームワークが基になるデータベースの悲観的ロックサポートをどのように使用するかについて詳しくは、こちらの記事をご覧ください。

楽観的ロック

オプティミスティックロックでは競合が発生しますが、バージョンが変更されたときにAliceのUPDATEを適用すると競合が検出されます。

アプリケーションレベルのトランザクション

今回は、追加のversion列があります。version列は、UPDATEまたはDELETEが実行されるたびにインクリメントされ、そしてそれはまたWHERE UPDATEの句とDELETE文で使用されています。これを機能させるにはversion、UPDATEまたはDELETEを実行する前にSELECTを発行して現在を読み取る必要があります。そうでない場合、WHERE句に渡すバージョン値またはインクリメントするバージョン値がわかりません。

データアクセスフレームワークが楽観的ロックを実装する方法の詳細については、こちらの記事をご覧ください。

アプリケーションレベルのトランザクション

リレーショナルデータベースシステムは70年代後半から80年代前半に登場し、クライアントは通常、ターミナルを介してメインフレームに接続していました。データベースシステムがSESSION設定などの用語を定義しているのは依然としてそのためです。

今日では、インターネットを介して、同じデータベーストランザクションのコンテキストで読み取りと書き込みを実行することはなくなり、ACIDでは十分ではなくなりました。

たとえば、次の使用例を考えてみます。

ここに画像の説明を入力してください

楽観的ロックがなければ、データベーストランザクションがSerializableを使用していても、この失われた更新がキャッチされることはありません。これは、読み取りと書き込みが別々のHTTPリクエストで実行されるため、異なるデータベーストランザクションで実行されるためです。

したがって、楽観的ロックは、ユーザー思考時間を組み込んだアプリケーションレベルのトランザクションを使用している場合でも、更新の損失を防ぐのに役立ちます。

アプリケーションレベルまたは論理トランザクションの詳細については、こちらの記事をご覧ください。

結論

オプティミスティックロックは非常に便利な手法であり、Read Committedなどの厳密性の低い分離レベルを使用している場合や、後続のデータベーストランザクションで読み取りと書き込みが実行されている場合でも問題なく機能します。

楽観的ロックの欠点はOptimisticLockException、をキャッチしたときにデータアクセスフレームワークによってロールバックがトリガーされるため、現在実行中のトランザクションによって以前に実行したすべての作業が失われることです。

競合が多いほど、競合が多くなり、トランザクションが中止される可能性が高くなります。テーブル行とインデックスレコードの両方を含む可能性のある現在の保留中の変更をすべて元に戻す必要があるため、データベースシステムのロールバックはコストがかかる可能性があります。

このため、トランザクションがロールバックされる可能性を減らすため、ペシミスティックロックは競合が頻繁に発生する場合に適しています。


OptimisticLockingとPessimisticLockingを選択するためにどのようなシナリオを提案しますか?OptimisticLockExceptionが発生する頻度に依存しますか?
Stimpson Cat

1
ユースケースによって異なります。ときどき、楽観的ロックが唯一の解決策です(たとえば、マルチリクエストトランザクション)。また、悲観的ロックが唯一の解決策である場合もあります(たとえば、PostgreSQLアドバイザリロック)。場合によっては、それらを組み合わせる必要がありますPESSIMISTIC_FORCE_INCREMENT
Vlad Mihalcea

22

悲観的ロックの方が良い選択になるもう1つのケースについて考えます。

楽観的ロックの場合、データ変更のすべての参加者は、この種のロックの使用に同意する必要があります。しかし、誰かがバージョン列を気にせずにデータを変更すると、楽観的ロックの全体的な概念が損なわれます。


楽観的および悲観的ロックを使用しようとする人々は、いわば、お互いの足を踏むこともできます。楽観的セッションがレコードを読み取り、悲観的セッションがレコードを更新している間にいくつかの計算を行っているシナリオを想像してください。その後、楽観的セッションが戻ってきて、行われた変更に気付かずに同じレコードを更新します。Select ... for updateは、すべてのセッションが同じ構文を使用している場合にのみ機能します。
2016

良い説明、私から投票してください
Dulaj Kulathunga

15

基本的に最も人気のある答えが2つあります。最初のものは基本的に言います

楽観的では3層アーキテクチャが必要ですが、必ずしもセッションのデータベースへの接続を維持する必要はありません。一方、悲観的ロックとは、使用が完了するまでレコードを排他的に使用するためにロックする場合です。データベースへの直接接続が必要な楽観的ロックよりもはるかに優れた整合性があります。

別の答えは

ロックがないため、楽観的(バージョニング)の方が高速ですが、競合が多い場合は(悲観的)ロックの方がパフォーマンスが高く、作業を破棄してやり直すよりも防止する方が適切です。

または

オプティミスティックロックは、まれな衝突がある場合に最適に機能します

置かれているように、このページに。

「接続を維持する」が「低衝突」にどのように関連するかを説明するために私の回答を作成しました。

どの戦略が最適かを理解するには、DBの1秒あたりのトランザクション数ではなく、1つのトランザクションの継続時間について考えてください。通常、トランザクションを開いて操作を実行し、トランザクションを閉じます。これは、ANSIが念頭に置いていた、ロッキングを回避するための細かい古典的なトランザクションです。しかし、多くのクライアントが同じ部屋/座席を同時に予約するチケット予約システムをどのように実装しますか?

あなたはオファーを閲覧し、利用可能な多くのオプションと現在の価格をフォームに記入します。時間がかかり、オプションが古くなる可能性があります。フォームに入力して[同意する]ボタンを押すまでのすべての価格が無効です。アクセスしたデータがロックされておらず、他の誰かがより機敏に干渉しているためですすべての価格を変更すると、新しい価格で再起動する必要があります。

代わりに、それらを読むときにすべてのオプションをロックすることができます。これは悲観的なシナリオです。なんでそれがひどいのかわかります。あなたのシステムは、予約を開始して喫煙する1人のピエロによってダウンさせることができます。終了するまでは誰も予約できません。キャッシュフローがゼロになります。そのため、実際には楽観的な予約が使用されます。あまりにも長い時間をかけていた人は、より高い価格で予約をやり直す必要があります。

この楽観的なアプローチでは、読み取ったすべてのデータを記録し(私の場合はRepeated Readのように)、データのバージョンを使用してコミットポイントに到達する必要があります(現在の価格ではなく、この見積もりに表示された価格で株式を購入したい)。この時点で、DBをロックするANSIトランザクションが作成され、何も変更されていないかどうかが確認され、操作がコミットまたは中止されます。IMO、これはMVCCの効果的なエミュレーションです、これもOptimistic CCに関連付けられており、アボートが発生した場合にトランザクションが再起動すること、つまり新しい予約を行うことを前提としています。ここでのトランザクションには、ユーザーによるユーザーの決定が含まれます。

私はMVCCを手動で実装する方法を理解するのには程遠いですが、再起動のオプションを備えた長時間実行トランザクションが主題を理解するための鍵だと思います。どこか間違っている場合は修正してください。私の答えは、このアレックスクズネコフ章によって動機付けられました。


12

ほとんどの場合、オプティミスティックロックはより効率的で、より高いパフォーマンスを提供します。悲観的ロックと楽観的ロックのどちらかを選択する場合は、次の点を考慮してください。

  • 悲観的ロックは、更新が多く、ユーザーが同時にデータを更新しようとする可能性が比較的高い場合に役立ちます。たとえば、各操作が一度に多数のレコードを更新できる場合(銀行は毎月末にすべてのアカウントに利息収入を追加する可能性があります)、2つのアプリケーションがそのような操作を同時に実行すると、競合が発生します。 。

  • 悲観的ロックは、頻繁に更新される小さなテーブルを含むアプリケーションにも適しています。これらのいわゆるホットスポットの場合、競合が発生する可能性が非常に高いため、楽観的ロックは競合するトランザクションのロールバックの労力を浪費します。

  • オプティミスティックロックは、競合が発生する可能性が非常に低い場合に役立ちます。レコードは多数ありますが、ユーザーが比較的少ないか、更新がほとんどなく、ほとんどが読み取りタイプの操作です。


3

楽観的ロックの使用例の1つは、アプリケーションでデータベースを使用して、スレッドまたはホストの1つがタスクを「要求」できるようにすることです。これは、私に定期的に役立つテクニックです。

私が考えることができる最良の例は、データベースを使用して実装されたタスクキューで、複数のスレッドが同時にタスクを要求している場合です。タスクのステータスが「Available」、「Claimed」、「Completed」の場合、dbクエリは「Set status = 'Claimed' where status = 'Available'」のように言うことができます。複数のスレッドがこの方法でステータスを変更しようとすると、ダーティデータのため、最初のスレッド以外はすべて失敗します。

これは楽観的ロックのみを含む使用例であることに注意してください。したがって、「衝突が予想されないときに楽観的ロックが使用される」と言う代わりに、衝突が予想されるが1つのトランザクションのみを成功させたい場合にも使用できます。


3

楽観的ロックと悲観的ロックについては、上で多くの良いことが述べられています。考慮すべき重要な点の1つは次のとおりです。

楽観的ロックを使用する場合、アプリケーションがこれらの障害からどのように回復するかということに注意する必要があります。

特に非同期メッセージ駆動型アーキテクチャーでは、これによりメッセージの処理が乱れたり、更新が失われたりする可能性があります。

障害のシナリオを十分に検討する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.