web-dev-qa-db-ja.com

MERGEはデッドロックとサーバーのブロックを防ぎますか?

アプリケーションでMERGEトランザクションを処理するためのオプションとしてUPSERTを検討し始めました。 SQL Serverの専門家の多くがこのアプローチを推奨しているようです。ただし、この方法を調査していると、MERGEが原因となる可能性のあるいくつかの一般的な問題が見つかりました。たとえば、 Use Caution with SQL Server's MERGE Statement by Aaron Bertrandです。

私のシステムは州全体であり、ColdFusionをバックエンドで使用しています。これはマルチスレッドであり、データベースへの大量のリクエストを期待しています。これらの各_INSERT/UPDATE_トランザクションは、単一行トランザクションを処理します。これは、ユーザーが一度に1行ずつ挿入または更新していることを意味します。ストアドプロシージャを呼び出すときに、ColdFusionでトランザクションとロールバックを使用しています。それで十分でしょうか、それともSQLでそれを実行する方が良いでしょうか?

以下は、ストアドプロシージャで使用されるMERGEステートメントの例です。

_CREATE PROCEDURE [dbo].[SaveMaster] 
   @RecordID INT = NULL, -- Auto increment ID
   @Status BIT = NULL,
   @Name VARCHAR(50) = NULL, 
   @Code CHAR(2) = NULL, --Primary Key
   @ActionDt DATETIME = NULL,
   @ActionID UNIQUEIDENTIFIER = NULL    
AS
   MERGE dbo.Master WITH (HOLDLOCK) AS Target
   USING (SELECT @RecordID,@Status,@Name,@Code,@ActionDt,@ActionID) 
   AS Source (RecordID,Status,Name,Code,ActionDt,ActionID)
      ON Target.RecID = Source.RecordID
   WHEN MATCHED THEN
      UPDATE
    SET Target.Status = Source.Status,
        Target.Name = Source.Name,
        Target.Code = Source.Code,
        Target.ActionDt = Source.ActionDt,
        Target.ActionID = Source.ActionID
   WHEN NOT MATCHED THEN
    INSERT(
        Status,Name,Code,ActionDt,ActionID
    )VALUES(
        Source.Status,
        Source.Name,
        Source.Code,
        Source.ActionDt,
        Source.ActionID
    )
OUTPUT inserted.RecID,$action as Action;
_

WITH(HOLDLOCK)を使用して、主キー違反とデッドロックを防止していることがわかります。私の投稿に添付されている記事を読んだ後、HOLDLOCKが残っていても、デッドロックに陥ることがあるようです。それが上記の私のコードにも当てはまるかどうかはわかりません。

_@RecordID_パラメータを使用して行が存在するかどうかを確認し、それに基づいてMERGEが挿入または更新を実行します。この列は自動インクリメントされたIDであり、テーブルの正しい行を常に更新するために、このIDをやり取りします。このアプローチは、私のシステムの複数のトランザクションで使用されています。

これで十分なのか、代わりに_Primary Key_(コードはPK)の使用を検討する必要がありますか?または、RecordID列とCode列の両方が含まれている可能性がありますか?それはパフォーマンスにまったく役立ちますか?

私はオプションと、SQLの世界でこれらのトランザクションの大きな問題のように思われるデッドロックとサーバーブロッキングを防止するための最も重要な試みを調査しています。誰かが私のコードについて意見や推奨事項を持っている場合は、私に知らせてください。

追加の考慮事項

挿入と更新を分離する必要があるというフィードバックを受け取ったので、さまざまな理由でMERGEを回避する必要があります。これが私が見つけた別のアプローチです。これらの2つのいずれかが良い解決策になるかどうか、主キー違反またはデッドロックに潜在的な問題があるかどうか疑問に思っています。

_begin tran
if exists (select * from table with (updlock,serializable) where key = @key)
begin
   update table set ...
   where key = @key
end
else
begin
   insert into table (key, ...)
   values (@key, ...)
end
commit tran
Or

2)

begin tran
   update table with (serializable) set ...
   where key = @key

   if @@rowcount = 0
   begin
      insert into table (key, ...) values (@key,..)
   end
commit tran
_

SQL Server 2008のアップグレードが必要であることは知っていますが、これは残念ながら私が制御できないものです。

4
espresso_coffee

上記の例では、WITH(HOLDLOCK)を使用して主キー違反とデッドロックを防止していることがわかります。

これは、同時MERGEによるPK違反を防ぐために必要であり、(通常)十分です。デッドロックはトランザクションのotherロックに依存しますが、MERGEの単純なクラスター化インデックスキーロックがある場合は問題ありません。

@RecordIDパラメータを使用してレコードが存在するかどうかを確認し、そのMERGEに基づいて挿入または更新を実行します。このフィールドは自動インクリメントされたIDであり、テーブル内の正しい行を常に更新するためにやり取りします。

それはかなり奇妙で、おそらく悪いことです。主キーを使用して、「常に正しい行を更新するようにしてください」。なぜあなたはその列があるのか​​わかりませんまったく、ましてそれをMERGEで使用することは言うまでもありません。 PKを使用するだけです。

コミュニティWikiの回答

事例として、MERGEは、別々のINSERTUPDATE、およびDELETEステートメントよりもパフォーマンスが低く、パフォーマンスの調整が難しいと報告されています。

ストアドプロシージャに個別のステートメントを確実に保持し、それらをトランザクションでラップすることを検討してください(これにより、保持されるロックが長くなる可能性があります)。 ColdFusionのトランザクション処理は避けてください。代わりにSQL Serverを信頼してください。

質問のIF EXISTSメソッドは劣っているようです。開始チェックは、WHEREUPDATE句が行うのと同じことを行います。 2番目の形式はかなり整然としています。ただし、適切なエラーとトランザクション処理のためにはTRY/CATCHが必要です( 簡単な例より包括的なアプローチ )。

あなたはいつも自分をデッドロックにコード化する可能性があります。 MERGEはそれを解決しません。ここではPK違反は発生しないはずですが、それがTRY/CATCHの目的です。 2つのトランザクションがまったく同じナノ秒にまったく同じキーを挿入しようとした場合、そのうちの1つが勝ち、TRY/CATCHを使用して他のトランザクションの処理方法を決定できます-再試行できます(どちらの場合も更新されます)、または強制的に失敗させ、競合があったことをユーザーが確認し、それに応じて行動できるようにします。 Daniel Farinaによる Implementing SQL Server Transaction Retry Logic for Failed Transaction を参照してください。

人々はブロッキングは悪いことだと思っていますが、これは正確にそれが目的です-2人が同じデータを同時に操作しようとするのを防ぐためです。 ブロックのブロックを心配している場合は(たとえば、ライターがリーダーをブロックしている場合など)、 row-versioningを確認する必要があります読み取りコミットスナップショット分離(RCSI)やスナップショット分離(SI)などの分離レベル

Michael Swartによる Be Be Care with the Merge StatementSQL Server UPSERT Patterns and Antipatterns もご覧ください。

1
user126897

Mergeステートメントは、特にSQL 2008の場合、ブロッキングや競合に対する特効薬ではありません(それは、後のバージョンで改善されました)。それらを考える最良の方法は、個別の挿入/更新ステートメントの構文糖としてです。そうは言っても、彼らは他の方法よりも目的のアクションを簡潔に説明しているので、可能な場合はそれらを好む傾向があります。

特にこの手順が一度に1つのレコードで機能するように見える場合、私が見つけた方法の1つは、以下で行ったようなことです。マージのターゲットにCTEを使用して、必要な特定のレコードを選択します。また、エンジンがロックをエスカレートさせないようにするために、ROWLOCKおよびUPDLOCkを使用してヒントを示します。

;WITH CTE_Target AS
        (
        SELECT RecID, Status, Name, Code, ActionDT, ActionID
        FROM dbo.Master WITH (ROWLOCK, UPDLOCK, HOLDLOCK)
        WHERE RecID = @RecordID
        )
    , CTE_Source AS
        (
        SELECT RecordID, Status, Name, Code, ActionDt, ActionID
        FROM (VALUES (@RecordID, @Status, @Name, @Code, @ActionDt, @ActionID)
                ) AS P (RecordID,Status,Name,Code,ActionDt,ActionID)
        )
   MERGE CTE_Target AS Target
   USING CTE_Source AS Source ON Target.RecID = Source.RecordID
   WHEN MATCHED THEN
      UPDATE
    SET Target.Status = Source.Status,
        Target.Name = Source.Name,
        Target.Code = Source.Code,
        Target.ActionDt = Source.ActionDt,
        Target.ActionID = Source.ActionID
   WHEN NOT MATCHED THEN
    INSERT(
        Status,Name,Code,ActionDt,ActionID
    )VALUES(
        Source.Status,
        Source.Name,
        Source.Code,
        Source.ActionDt,
        Source.ActionID
    )
OUTPUT inserted.RecID,$action as Action;

ただし、前述したように、デッドロックが発生する可能性はまだあり、マルチスレッド環境では解決が難しい場合があります。 1つの方法は、ステートメントを高速化して、デッドロックが発生するウィンドウを減らすことです。

0
Jonathan Fite