2016-01-08 1 views
7

UPDATE documentationによれば、UPDATEは常にテーブル全体の排他ロックを獲得します。しかし、更新される行が決定される前、または実際の更新の直前に排他ロックが取得されるかどうかは疑問です。単一のテーブルのみを更新するトランザクションは常に分離されていますか?

私の具体的な問題は、私はこのように私のUPDATEでネストされたSELECTしていることである。

UPDATE Tasks 
SET Status = 'Active' 
WHERE Id = (SELECT TOP 1 Id 
      FROM Tasks 
      WHERE Type = 1 
       AND (SELECT COUNT(*) 
        FROM Tasks 
        WHERE Status = 'Active') = 0 
      ORDER BY Id) 

は今、私は本当に並列であれば、その後Status = 'Active'と正確に一つの タスクがあることが保証されているかどうかを疑問に思って

UPDATE Tasks 
SET Status = 'Active' 
WHERE Id = (SELECT TOP 1 Id 
      FROM Tasks 
      WHERE Type = 2   -- <== The only difference 
       AND (SELECT COUNT(*) 
        FROM Tasks 
        WHERE Status = 'Active') = 0 
      ORDER BY Id) 

両方のステートメントでロックを取得する前に変更する行が決定された場合は、私が避けなければならない2つの活動があります。

この場合、どのように防止できますか?トランザクションレベルをSERIALIZABLEに設定したり、ロックヒントをつぶしたりせずにそれを防ぐことはできますか?

Is a single SQL Server statement atomic and consistent?の回答から、入れ子になったSELECTが別のテーブルにアクセスするときに問題が発生することがわかりました。しかし、更新されたテーブルだけが関係している場合、この問題を気にする必要があるかどうかはわかりません。

+1

ドキュメントはとにかく間違っていること。通常、更新はテーブル全体をロックしません。 –

+0

ええ、ありがとう、ありがとう。しかし、正しい文書はどこにありますか? – lex82

+0

文書は、実際には 'UPDATE'がテーブル全体をロックしているとは言いません。それは、排他ロックを取得すると言いますが、排他ロックはテーブル全体にある必要はありません。 –

答えて

0

いいえ、少なくともネストされたSELECT文は、更新が開始され、ロックが取得される前に処理できます。この更新プログラムに他のクエリが干渉しないようにするには、トランザクション分離レベルをSERIALIZABLEに設定する必要があります。

この記事(そしてシリーズ、それはの一部である)は非常によくSQLサーバーでの同時実行の機微を説明しています

http://sqlperformance.com/2014/02/t-sql-queries/confusion-caused-by-trusting-acid

1

これではない、あなたの質問に答え...しかし、あなたのクエリを使用すると、アクティブ=静的で正確に一つのタスクをしたい場合は、これを確実にするためにテーブルを設定私の目:)

;WITH cte AS 
(
    SELECT *, RowNum = ROW_NUMBER() OVER (PARTITION BY [type] ORDER BY id) 
    FROM Tasks 
) 
UPDATE cte 
SET [Status] = 'Active' 
WHERE RowNum = 1 
    AND [type] = 1 
    AND NOT EXISTS(
      SELECT 1 
      FROM Tasks 
      WHERE [Status] = 'Active' 
     ) 
+1

OPの元のクエリがこのバージョンより効率的である可能性があります。注:OPの解決策は '(SELECT COUNT(*)。。。)の代わりに' NOT EXISTS'を使用する必要があります。 –

+0

RowNumが好きですが、IsActiveは特定の行に関連していないので混乱しています。私は条件付きのステートメントを使用しますが、テーブルがロックされないため、これが回避したい問題を正確に生じることは間違いありません。 – lex82

+0

@Gordon Linoffは、ノート – Devart

6

ための痛みです本当です。フィルター一意のインデックスを使用します。

create unique index unq_tasks_status_filter_active on tasks(status) 
    where status = 'Active'; 

秒同時updateが失敗することがありますが、あなたは一意性を確保することとしております。アプリケーションコードは、このような失敗した更新を処理し、再試行することができます。

更新プログラムの実際の実行計画を信頼することは危険です。そのため、データベースにそのような検証を行う方が安全です。基本的な実装の詳細は、SQL Serverの環境とバージョンによって異なる場合があります。たとえば、単一スレッドの単一プロセッサ環境で動作するものは、並列環境では動作しない可能性があります。 1つの分離レベルで動作するものは、別の分離レベルでは動作しません。

編集:

そして、私は耐えられません。その後Tasks(Status)Tasks(Type, Id)のインデックスを配置

UPDATE Tasks 
    SET Status = 'Active' 
    WHERE NOT EXISTS (SELECT 1 
         FROM Tasks 
         WHERE Status = 'Active' 
        ) AND 
      Id = (SELECT TOP 1 Id 
       FROM Tasks 
       WHERE Type = 2   -- <== The only difference 
       ORDER BY Id 
       ); 

:効率のために、としてクエリを書くことを検討してください。実際、適切なクエリでは、クエリが非常に速く(インデックスの更新にもかかわらず)、現在の更新に関する心配が大幅に軽減されることがあります。これは競合状態を解決することはできませんが、少なくともそれは稀です。

そして、あなたがエラーをキャプチャしている場合は、そのユニークなフィルターのインデックスで、あなただけ行うことができます:行がすでにアクティブである場合

UPDATE Tasks 
    SET Status = 'Active' 
    WHERE Id = (SELECT TOP 1 Id 
       FROM Tasks 
       WHERE Type = 2   -- <== The only difference 
       ORDER BY Id 
       ); 

これはエラーを返します。

注:これらのクエリおよび概念はすべて、「1つのグループにつき1つのアクティブ」に適用できます。この回答はあなたが尋ねた質問に対処しています。 「グループごとに1つのアクティブな」問題がある場合は、別の質問をすることを検討してください。

+0

これを指摘してくれてありがとう!残念ながら、私が働いている環境ではこれは不可能です。私の声明はとにかく簡素化されていますので、もし私がそれを設定することができれば、そのような指標が役立つかどうかわかりません。私は、UPDATEの処理方法を理解したいと思っています。 – lex82

+1

@ lex82 - 簡略化がたとえば(1つまたは複数の他の列の組み合わせ)アクティブなパーツが1つしかない場合は、 'status'ではなく、その列をインデックスに入れるだけです。 –

関連する問題