web-dev-qa-db-ja.com

特定の列を変更するためにSQLServerで同時実行を処理する

競合状態に苦しんでいるテーブルがあり、それを修正する方法がわかりません。コードで100%解決できないので、サーバーが必要です。

シナリオ

システムの管理者は、受け入れるまたは拒否予定を立てることができます。これは、電子メール(トークンなどを介して)またはシステムにログインして管理パネルを介して行うことができます。

この問題は、2人の管理者が同時に実行すると発生します。

t = 0:
admin [〜#〜] a [〜#〜]クリックacceptシステムメールから;
admin [〜#〜] b [〜#〜]クリック拒否管理パネルから;

t = 1:
バックエンドチェック予定はすでに処理されていますか?
これは両方の管理者で同時に発生するため、両方ともテストに合格します-> 予定は承認を待っています...

t = 2:
バックエンド[〜#〜] a [〜#〜]クエリ "update status to accepted"を送信します
バックエンド[〜#〜] b [〜#〜]クエリ "update status to rejected"を送信します

t = 3:両方のSQLがサーバーにヒットします。
サーバーは、予定をacceptedに変更してから、2番目のクエリの実行に進み、予定をrejectedに変更します。

このテーブルはトラフィックが多いため、ロックすることはできません。

私の問題を解決できると思うのは、次のとおりです。
最初:クエリがありますロック行;
second:ステータス列をチェックして、すでに設定されているかどうかを確認します。
third:設定されていない場合は、値を設定します。
4番目:ロックを解除します。


その間、2番目のクエリはロックを取得しようとし続けます。成功した後、値がすでに設定されているかどうかを確認し、実際に設定されていることを確認して、エラーなどを返します。

SQL Serverでこのようなことを行うにはどうすればよいですか?

フローは次のようになります。

while (can't read) {

    try to acquire lock;
}

// lock acquired.
read appointment from table;
if value is null, set the value;
release lock;

フレームワークがデフォルトとしてREAD_COMMITTED_SNAPSHOTを使用していることがわかりました。

2
victor

同様の要件があったため、クエリにwhere句を追加しました。例えば。

UPDATE AppointmentTable 
SET STatus = [Approved/Rejected] 
WHERE AppointmentID = @ID AND Status = Pending

その後、更新された行数を確認します。 1行が更新された場合は、アクションが記録されたことをユーザーに報告します。 0行が更新された場合は、他の誰かがすでに作業を行ったことをユーザーに報告してください。

クエリが「同時に」実行される可能性はありませんか

いいえ。それらが「同時に」実行された場合でも、そのうちの1つは最初に更新ロックを取得します。もう一方は、更新ロックを待つ必要があります。 2番目がロックを取得しても、それがまだtrueであることを検証するためにwhere句をチェックする必要があります。 trueでなくなった場合、その行はスキップされ、更新は行われません(@@ rowcount = 0)。重要なのは、where句に古い値を含めることです。

4
Greg

@Kinが示したように、ソリューションは、使用しているトランザクション分離レベルに依存します。 DBAはその質問に答えられるはずです。昔ながらのREAD_COMMITTED(デフォルト)を使用していて、SNAPSHOT分離を使用していない場合は、次のことができるはずです。

  • トランザクションの開始
  • UPDATE AppointmentTable SET AppointmentStatus = AppointmentStatus WHERE ID = @ ID(これにより、行に更新ロックが作成されますが、すでに存在する同じ値に更新しているため、まだ何も変更されていません。他の誰かがこの行を更新している場合、トランザクションは更新が完了するのを待ちます)
  • SELECT AppointmentStatus FROM AppointmentTable(他の誰かがこの行を更新している場合、トランザクションは更新が完了するのを待ちます。
  • AppointmentStatus IN( 'Accept'、 'Reject')の場合、これまで実際に何も更新されていなくても、作業をロールバックできます。
  • AppointmentStatus NOT IN( 'Accept'、 'Reject')の場合、UPDATE AppointmentTable SET AppointmentStatus = 'AcceptまたはReject'
  • トランザクションをコミットする
0
Scott Hodgin