web-dev-qa-db-ja.com

具体的な(Java)例による楽観的ロック

私は午前中すべてのトップ記事を読んで過ごしました Googleは楽観的ロックを解き放ちます 、そして私の人生において、私はまだ本当にそれを理解していません。

I nderstand楽観的ロックには、レコードの "バージョン"を追跡するための列の追加が含まれ、この列はタイムスタンプ、カウンター、またはその他のバージョン追跡構成にすることができます。しかし、それでもWRITE整合性がどのように保証されるかはまだわかりません(つまり、複数のプロセスが同じエンティティを同時に更新している場合、その後、エンティティは本来あるべき状態を正しく反映します)。

誰かがJava(againstに対して)楽観的ロックをどのように使用できるかの例を具体的でわかりやすいの例で提供できますか? 、たぶん、MySQL DB)Personエンティティがあるとします。

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private Color favoriteColor;
}

そして、そのPersonインスタンスがpeople MySQLテーブルに永続化されます。

CREATE TABLE people (
    person_id PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,        # } I realize these column defs are not valid but this is just pseudo-code
    age INT NOT NULL,
    color_id FOREIGN KEY (colors) NOT NULL  # Say we also have a colors table and people has a 1:1 relationship with it
);

次に、同じPersonエンティティを同時に更新しようとしている2つのソフトウェアシステム、または2つのスレッドを備えた1つのシステムがあるとします。

  • ソフトウェア/スレッド#1は姓の変更を永続化しようとしています( "John Smith"から "John Doe"へ))
  • ソフトウェア/スレッド#2は、お気に入りの色の変更を持続しようとしています([〜#〜] red [〜#〜]から[〜#〜] green [〜#〜 ]

私の質問:

  1. peopleテーブルやcolorsテーブルに楽観的ロックを実装するにはどうすればよいですか? (特定のDDL例を探す)
  2. 次に、この楽観的ロックをアプリケーション/ Javaレイヤーでどのように利用できますか? (特定のコード例を探す)
  3. DDL /コードの変更(上記の#1と#2から)が私のシナリオ(または他のシナリオ)で機能し、people/colorsテーブルを正しく "最適にロック"するシナリオを誰かが実行できますか?基本的に、楽観的ロックの動作を見て、なぜ機能するのかをわかりやすく説明しています。
29

通常、楽観的ロックを調べるときは、 Hibernate などのライブラリ、または _@Version_ をサポートする他のJPA実装も使用します。

例は次のように読むことができます:

_public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private Color favoriteColor;
    @Version
    private Long version;
}
_

これをサポートするフレームワークを使用していない場合は、明らかに_@Version_アノテーションを追加する意味はありません。

DDLは

_CREATE TABLE people (
    person_id PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,        # } I realize these column defs are not valid but this is just pseudo-code
    age INT NOT NULL,
    color_id FOREIGN KEY (colors) NOT NULL,  # Say we also have a colors table and people has a 1:1 relationship with it
    version BIGINT NOT NULL
);
_

バージョンはどうなりますか?

  1. エンティティを保存する前に、データベースに保存されているバージョンが、まだ知っているバージョンかどうかを確認します。
  2. そうであれば、バージョンを1つ増やしてデータを保存します。

他のプロセスが両方のステップ間でデータを変更するリスクを冒すことなく両方のステップを実行するには、通常、次のようなステートメントで処理されます。

_UPDATE Person SET lastName = 'married', version=2 WHERE person_id = 42 AND version = 1;
_

ステートメントを実行した後、行を更新したかどうかを確認します。変更した場合、データを読んだので誰もデータを変更しませんでした。それ以外の場合は、他の誰かがデータを変更しました。他の誰かがデータを変更した場合、通常、使用しているライブラリによって OptimisticLockException を受け取ります。

この例外により、すべての変更が取り消され、値を変更するプロセスが再開されます。これは、エンティティーが更新される条件が適用されなくなる可能性があるためです。

だから衝突はない:

  1. プロセスAはPersonを読み取ります
  2. プロセスAはPersonを書き込み、それによってバージョンをインクリメントします
  3. プロセスBはPersonを読み取ります
  4. プロセスBはPersonを書き込み、バージョンをインクリメントします

衝突:

  1. プロセスAはPersonを読み取ります
  2. プロセスBはPersonを読み取ります
  3. プロセスAはPersonを書き込み、それによってバージョンをインクリメントします
  4. Personが読み取られてからバージョンが変更されたときに保存しようとすると、プロセスBが例外を受け取ります

Colorが別のオブジェクトの場合は、同じスキームでバージョンを配置する必要があります。

楽観的ロックとは何ですか?

  • オプティミスティックロックは、競合する変更をマージする魔法ではありません。楽観的ロックは、プロセスが別のプロセスによる変更を誤って上書きするのを防ぐだけです。
  • 楽観的ロックは実際には実際のDBロックではありません。バージョン列の値を比較するだけで機能します。他のプロセスがデータにアクセスするのを妨げないため、OptimisticLockExceptionsを取得することが期待されます

バージョンとして使用する列タイプは何ですか?

多くの異なるアプリケーションがデータにアクセスする場合は、データベースによって自動的に更新される列を使用するのが最善の方法です。例えばMySQLの場合

_version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
_

このように、楽観的ロックを実装するアプリケーションは、ダムアプリケーションによる変更に気づきます。

TIMESTAMPの解像度またはそれのJava解釈よりも頻繁にエンティティを更新すると、このアプローチは特定の変更の検出に失敗する可能性があります。また、Java新しいTIMESTAMPを生成させる場合は、アプリケーションを実行しているすべてのマシンが完全に時間同期していることを確認する必要があります。

すべてのアプリケーションを整数、ロング、...に変更できる場合、通常はバージョンが適切なソリューションです。これは、クロックの設定が異なることによる影響を受けないためです;-)

他のシナリオがあります。たとえば、行を変更するたびに、ハッシュを使用するか、Stringをランダムに生成します。重要なのは、プロセスがバージョン列を確認しても変更を検出できないため、プロセスがローカル処理またはキャッシュ内にデータを保持している間は値を繰り返さないことです。

最後の手段として、すべてのフィールドの値をバージョンとして使用できます。これはほとんどの場合で最も費用のかかるアプローチですが、テーブル構造を変更せずに同様の結果を得る方法です。 Hibernateを使用する場合、この動作を強制する _@OptimisticLocking_ -annotationがあります。エンティティの読み取り後に行が変更された場合にエンティティクラスで@OptimisticLocking(type = OptimisticLockType.ALL)を使用して失敗するか、別のプロセスが変更したフィールドを変更したときに@OptimisticLocking(type = OptimisticLockType.DIRTY)だけで失敗します。

39
TheConstructor

@TheConstructor:それが何であるか、そうでないかについての素晴らしい説明。 「オプティミスティックロックは競合する変更をマージする魔法ではない」と言ったとき、私はコメントしたいと思いました。以前は、ユーザーがフォーム上のレコードを編集できるDataFlexアプリケーションを管理していました。ユーザーが「保存」ボタンを押すと、アプリはデータの「マルチユーザー再読み取り」と呼ばれる処理を実行し、現在の値を取得して、ユーザーが変更した内容と比較します。ユーザーが変更したフィールドがその間に変更されていなかった場合、それらのフィールドだけがレコードに書き戻され(再読み取り+書き込み操作中にのみロックされます)、したがって、2人のユーザーが異なるフィールドを透過的に変更できます問題のない同じレコード。バージョンスタンプは不要で、どのフィールドが変更されたかを知るだけで済みました。

確かに、これは完璧な解決策ではありませんが、その場合はうまくいきました。それは楽観的であり、無関係な変更を許可し、競合する変更についてユーザーにエラーを与えました。これは、SQLを実行する前に実行できる最高の方法でしたが、おそらくオブジェクトやWeb関連のシナリオでは、今日でも優れた設計原則です。

4
no comprende