アプリケーションで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のアップグレードが必要であることは知っていますが、これは残念ながら私が制御できないものです。
上記の例では、WITH(HOLDLOCK)を使用して主キー違反とデッドロックを防止していることがわかります。
これは、同時MERGEによるPK違反を防ぐために必要であり、(通常)十分です。デッドロックはトランザクションのotherロックに依存しますが、MERGEの単純なクラスター化インデックスキーロックがある場合は問題ありません。
@RecordIDパラメータを使用してレコードが存在するかどうかを確認し、そのMERGEに基づいて挿入または更新を実行します。このフィールドは自動インクリメントされたIDであり、テーブル内の正しい行を常に更新するためにやり取りします。
それはかなり奇妙で、おそらく悪いことです。主キーを使用して、「常に正しい行を更新するようにしてください」。なぜあなたはその列があるのかわかりませんまったく、ましてそれをMERGEで使用することは言うまでもありません。 PKを使用するだけです。
事例として、MERGE
は、別々のINSERT
、UPDATE
、およびDELETE
ステートメントよりもパフォーマンスが低く、パフォーマンスの調整が難しいと報告されています。
ストアドプロシージャに個別のステートメントを確実に保持し、それらをトランザクションでラップすることを検討してください(これにより、保持されるロックが長くなる可能性があります)。 ColdFusionのトランザクション処理は避けてください。代わりにSQL Serverを信頼してください。
質問のIF EXISTS
メソッドは劣っているようです。開始チェックは、WHERE
のUPDATE
句が行うのと同じことを行います。 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 Statement と SQL Server UPSERT Patterns and Antipatterns もご覧ください。
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つの方法は、ステートメントを高速化して、デッドロックが発生するウィンドウを減らすことです。