web-dev-qa-db-ja.com

MySQLの楽観的ロック

MySQLの楽観的ロックに関する詳細は見つかりません。トランザクションを開始すると、2つのエンティティの更新が同期されたままになると読みました。ただし、2人のユーザーが同時にデータを更新しても、競合は発生しません。

どうやら楽観的ロックはこの問題を解決しますか?これはMySQLでどのように適用されますか。このためのSQL構文/キーワードはありますか?それともMySQLにはデフォルトの動作がありますか?

みんなありがとう。

35
Green Acorn

ポイントは、楽観的ロックはMySQLやその他のデータベース機能ではなく、データベース機能ではないということです。楽観的ロックは、標準の命令でDBを使用して適用される手法です。

非常に単純な例で、複数のユーザー/クライアントが同時に実行できるコードでこれを実行するとします。

  1. 1つのIDフィールド(iD)と2つのデータフィールド(val1、val2)を持つ行からデータを選択する
  2. オプションでデータを使用して計算を行う
  3. その行のデータを更新する

ロックしない方法は:

注:すべてのコード{中括弧の間}は、SQL側ではなく、(必ずしも)アプリコード内にあることを意図しています

- SELECT iD, val1, val2
       FROM theTable
       WHERE iD = @theId;
 - {code that calculates new values}
 - UPDATE theTable
       SET val1 = @newVal1,
           val2 = @newVal2
       WHERE iD = @theId;
 - {go on with your other code}

OPTIMISTIC LOCKINGの方法は次のとおりです。

- SELECT iD, val1, val2
       FROM theTable
       WHERE iD = @theId;
 - {code that calculates new values}
 - UPDATE theTable
       SET val1 = @newVal1,
           val2 = @newVal2
       WHERE iD = @theId
           AND val1 = @oldVal1
           AND val2 = @oldVal2;
 - {if AffectedRows == 1 }
 -     {go on with your other code}
 - {else}
 -     {decide what to do since it has gone bad... in your code}
 - {endif}

重要な点は、UPDATE命令の構造と影響を受ける行のチェックの後続の数にあることに注意してください。 SELECTとUPDATEを実行したときに、誰かがすでにデータを変更していることをコードに認識させるのは、これら2つのことです。すべてがトランザクションなしで行われたことに注意してください!これは非常に単純な例であるという理由だけで(トランザクションが存在しない場合)可能でしたが、これは楽観的ロックの要点がトランザクション自体にないことも示しています。

では、トランザクションについてはどうですか?

 - SELECT iD, val1, val2
       FROM theTable
       WHERE iD = @theId;
 - {code that calculates new values}
 - BEGIN TRANSACTION;
 - UPDATE anotherTable
       SET col1 = @newCol1,
           col2 = @newCol2
       WHERE iD = @theId;
 - UPDATE theTable
       SET val1 = @newVal1,
           val2 = @newVal2
       WHERE iD = @theId
           AND val1 = @oldVal1
           AND val2 = @oldVal2;
 - {if AffectedRows == 1 }
 -     COMMIT TRANSACTION;
 -     {go on with your other code}
 - {else}
 -     ROLLBACK TRANSACTION;
 -     {decide what to do since it has gone bad... in your code}
 - {endif}

この最後の例は、ある時点で衝突をチェックし、すでに他のテーブル/行を変更したときに衝突が発生したことを発見した場合、トランザクションを使用して、それ以降に行ったすべての変更をロールバックできることを示しています。始まり。可能なコリジョンごとにロールバックする操作の量を決定するのは(アプリケーションが何をしているかを知っている)あなた次第です。これに基づいて、トランザクション境界を配置する場所とスペシャルとのコリジョンをチェックする場所を決定します。 UPDATE + AffectedRowsチェック。

この場合のトランザクションでは、UPDATEを実行する瞬間とコミットされる瞬間を分けています。では、「他のプロセス」がこの時間枠で更新を実行するとどうなりますか?何が起きているかを正確に知るには、分離レベル(および各エンジンでの管理方法)の詳細を掘り下げる必要があります。例として、READ_COMMITTEDを使用するMicrosoft SQL Serverの場合、更新された行はCOMMITまでロックされるため、「他のプロセス」はその行に対して何も実行できず(待機し続け)、SELECTも実行できません(実際にはREAD_COMMITTEDしか実行できません)。 。そのため、「その他のプロセス」のアクティビティは遅延されるため、そのUPDATEは失敗します。

VERSIONING OPTIMISTIC LOCKINGオプション:

 - SELECT iD, val1, val2, version
       FROM theTable
       WHERE iD = @theId;
 - {code that calculates new values}
 - UPDATE theTable
       SET val1 = @newVal1,
           val2 = @newVal2,
           version = version + 1
       WHERE iD = @theId
           AND version = @oldversion;
 - {if AffectedRows == 1 }
 -     {go on with your other code}
 - {else}
 -     {decide what to do since it has gone bad... in your code}
 - {endif}

ここでは、すべてのフィールドの値が同じかどうかを確認する代わりに、専用フィールド(UPDATEを実行するたびに変更される)を使用して、誰もが私よりも速く、行間の行を変更したかどうかを確認できますSELECTおよびUPDATE。ここで、トランザクションがないのは、最初の例のように単純であるためであり、バージョン列の使用とは関係ありません。繰り返しになりますが、この列の使用はアプリケーションコードの実装次第であり、データベースエンジンの機能ではありません。

これ以外にも、この答えを長くしすぎる(すでに長すぎる)と思う他のポイントがあります。

  • sELECTに対するトランザクションの影響に関するトランザクション分離レベル( ここではMySQL )。
  • 自動生成されていない主キー(または一意の制約)を持つテーブルでのINSERTの場合、2つのプロセスが一意である必要がある場所に同じ値を挿入しようとするかどうかを特にチェックする必要なく、自動的に失敗します。
  • id列(主キーまたは一意の制約)がない場合も、単一のSELECT + UPDATEでトランザクションが必要になります。他のユーザーが変更を加えた後、UPDATEのWHERE句の基準に一致する行が予想より多くなるという驚きがあるためです。

実際に確認して自信を持つ方法

分離レベルの値と実装は異なる可能性があるため、(このサイトでは通常のように)使用するプラットフォーム/環境でテストを実行することをお勧めします。

難しいように思えるかもしれませんが、実際には、2つの個別のウィンドウを使用し、トランザクションを1つずつ開始し、コマンドを1つずつ実行して、DB開発環境から非常に簡単に実行できます。

ある時点で、コマンドの実行が無期限に続くことがわかります。次に、他のウィンドウでCOMMITまたはROLLBACKと呼ばれると、実行が完了します。

ここでは、説明したとおりにテストできる、非常に基本的なコマンドをいくつか示します。

これらを使用して、テーブルと1つの有用な行を作成します。

CREATE TABLE theTable(
    iD int NOT NULL,
    val1 int NOT NULL,
    val2 int NOT NULL
)
INSERT INTO theTable (iD, val1, val2) VALUES (1, 2 ,3);

次に、2つの異なるウィンドウで次の手順を実行します。

BEGIN TRAN

SELECT val1, val2 FROM theTable WHERE iD = 1;

UPDATE theTable
  SET val1=11
  WHERE iD = 1 AND val1 = 2 AND val2 = 3;

COMMIT TRAN

次に、コマンドの順序と実行の順序を、考えられる任意の順序で変更します。

118
Diego Mazzaro