web-dev-qa-db-ja.com

SELECT-UPDATEパターンを使用する場合の同時実行性の管理

次のコードがあるとします(それがひどいことを無視してください)。

_BEGIN TRAN;
DECLARE @id int
SELECT @id = id + 1 FROM TableA;
UPDATE TableA SET id = @id; --TableA must have only one row, apparently!
COMMIT TRAN;
-- @id is returned to the client or used somewhere else
_

私の目には、これは並行性を適切に管理していません。トランザクションがあるからと言って、更新ステートメントに到達する前に行ったのと同じ値を他の誰かが読み取らないというわけではありません。

ここで、コードをそのままにします(これは単一のステートメントとして処理するか、autoincrement/identity列を使用する方が適切です)。並行性を適切に処理し、2つのクライアントが同じになる競合状態を防ぐための確実な方法は何ですか。 id値?

SELECTにWITH (UPDLOCK, HOLDLOCK)を追加するとうまくいくと確信しています。 SERIALIZABLEトランザクション分離レベル は、トランザクションが終了するまで他のユーザーが何をしたかを読み取ることを拒否するため、同様に機能するようです([〜#〜] update [ 〜#〜]:これは誤りです。マーティンの回答を参照してください)。本当?どちらも同じようにうまく機能しますか?どちらが優先されますか?

IDの更新よりも正当なものを行うことを想像してください。更新に必要な読み取りに基づいた計算です。多くのテーブルが含まれる可能性があり、書き込みを行うテーブルと行わないテーブルがあります。ここでのベストプラクティスは何ですか?

この質問を書いた後、必要なテーブルのみをロックするので、ロックヒントの方が良いと思いますが、どなたかご意見をいただければ幸いです。

追伸そして、いいえ、私は最良の答えを知りません、そして本当により良い理解を得たいです! :)

25
ErikE

SERIALIZABLE分離レベルの側面に対処するだけです。はい、これは機能しますが、デッドロックのリスクがあります。

2つのトランザクションは両方とも同時に行を読み取ることができます。テーブル構造とこれらのロックに依存するオブジェクトSロックまたはインデックスRangeS-Sロックを取得するため、これらは互いにブロックしません 互換性があります 。ただし、更新に必要なロック(オブジェクトIXロックまたはインデックスRangeS-U)を取得しようとすると、お互いにブロックされ、デッドロックが発生します。

代わりに明示的なUPDLOCKヒントを使用すると、読み取りがシリアル化され、デッドロックのリスクが回避されます。

11
Martin Smith

あなたにとって最良のアプローチは、実際にモジュールを高い並行性に公開し、自分自身で確認することだと思います。 UPDLOCKだけで十分な場合もあり、HOLDLOCKは必要ありません。 sp_getapplockが非常にうまく機能する場合があります。ここでは何も触れません。インデックス、トリガー、またはインデックス付きビューを1つ追加すると、結果が変わる場合があります。テストコードにストレスをかけ、ケースバイケースで自分自身で確認する必要があります。

ストレステストの例をいくつか書きました ここ

Edit:内部のより良い知識のために、Kalen Delaneyの本を読むことができます。ただし、他のドキュメントと同様に、書籍の同期が取れなくなる可能性があります。そのうえ、考慮しなければならない組み合わせが多すぎます。6つの分離レベル、多くの種類のロック、クラスター化/非クラスター化インデックス、他に何を知っているかなどです。それはたくさんの組み合わせです。その上、SQL Serverはクローズドソースであるため、ソースコードをダウンロードしたり、デバッグしたりすることはできません。これが最終的な知識のソースになります。それ以外のものは、次のリリースまたはサービスパックの後で不完全になるか、古くなる可能性があります。

したがって、独自のストレステストを行わずに、システムで何が機能するかを判断しないでください。読んだことが何であれ、何が起こっているのかを理解するのに役立ちますが、読んだアドバイスがあなたに役立つことを証明する必要があります。誰かがあなたのためにそれをすることはできないと思います。

11
A-K

この特定のケースでは、UPDLOCKロックをSELECTに追加すると、異常が実際に防止されます。トランザクション中に更新ロックが保持されるため、HOLDLOCKを追加する必要はありませんが、私はそれを過去の(おそらく悪い)習慣として含めることを認めています。

IDの更新よりも正当な何か、更新に必要な読み取りに基づく計算を行うことを想像してください。多くのテーブルが含まれる可能性があり、書き込みを行うテーブルと行わないテーブルがあります。ここでのベストプラクティスは何ですか?

ベストプラクティスはありません。同時実行制御の選択は、アプリケーションの要件に基づく必要があります。一部のアプリケーション/トランザクションは、データベースを排他的に所有しているかのように実行する必要があり、異常や不正確さをすべてのコストで回避します。他のアプリケーション/トランザクションは、ある程度の相互干渉を許容できます。

  • Webストア内の製品のバンド付き在庫レベル(<5、10 +、50 +、100 +)の取得=ダーティリード(不正確は関係ありません)。
  • そのWebストアのチェックアウトで在庫レベルを確認して削減する=繰り返し読み取り(販売前に在庫を確保する必要があります。在庫レベルがマイナスになることはありません)。
  • 銀行の現在の口座と普通預金の間で現金を移動する=シリアル化可能(誤算したり、現金を置き忘れたりしないでください!).

編集:@AlexKuznetsovのコメントにより、質問が再読され、回答の非常に明らかなエラーが削除されました。夜遅くに投稿する際は注意してください。

9