From: SQL Server 2014同時入力の問題
補足質問:
競合状態やデッドロックなどを回避しながら、並列マルチスレッド環境で行を削除および再挿入するにはどうすればよいですか? (UPDLOCK, SERIALIZABLE
)または別のロック?
Order
とOrderLineDetail
の2つのテーブルがあります。テーブルOrder
は、一般的な情報を格納する親です。 OrderLineDetail
は子テーブルです。これは、注文に複数の詳細ラインアイテムがあるためです。
注文は更新できるため、最初にOrderId
がテーブルに存在するかどうかを確認し、それに応じて挿入または更新します。
顧客の変更、回線の問題、サーバーのビジーなどにより、注文ファイルは複数回送信される可能性があります。 time Lastfiledatetime
列があります。着信タイムスタンプが古い場合、テーブルには影響しません。
OrderLineDetail
テーブルの場合、ファイルに自然キーまたはサロゲートキーがない場合があるため、すべての子OrderLineDetail
アイテムを削除して再入力します(これは古いレガシーシステムのxmlファイルです) )。
Create Table Orders
(OrderId bigint primary key, -- this is in xml files
Lastfiledatetime datetime null
)
Create Table OrderLineDetail
(OrderLineDetailid bigint primary key identity(1,1), -- this is Not in the xml files
OrderLineDescription varchar(50),
OrderLineQuantity int,
OrderId bigint foreign key references Orders(OrderId),
Lastfiledatetime datetime
)
SQL Server 2016 Read Committed Snapshot Isolationレベルを使用して、マルチスレッドの並列処理環境で作業します。
Order
親を更新するには、次のようにします。
BEGIN TRANSACTION;
IF NOT EXISTS
(
SELECT *
FROM Order WITH (UPDLOCK, SERIALIZABLE)
WHERE Order ID=@OrderID
)
INSERT INTO Order () VALUES ()
ELSE
UPDATE Order
SET ...
WHERE OrderID=@OrderID
AND LastFileDatetime<@CreatedTime;
COMMIT TRANSACTION;
競合状態やデッドロックなどを回避しながら、並列マルチスレッド環境で子OrderLineDetail
テーブルを削除して再挿入するにはどうすればよいですか? (UPDLOCK, SERIALIZABLE
)または別のロック?
BEGIN TRANSACTION;
IF EXISTS
(
SELECT *
FROM OrderLineDetail WITH (UPDLOCK, SERIALIZABLE)
WHERE Order ID=@OrderID
)
DELETE OrderLineDetail
WHERE OrderId=@OrderId
AND LastFileDatetime<@CreatedTime;
INSERT INTO OrderLineDetail () VALUES ()
COMMIT TRANSACTION;
UPDLOCK, SERIALIZABLE
パターンは、UPSERT
と呼ばれる特に一般的な操作を実行するときに競合状態による誤った結果(誤ったキー違反エラーを含む)を回避するためのものです-既存の行が存在する場合はそれを更新します;それ以外の場合は、新しい行を挿入します。
...レーシングコンディション、デッドロックなどを回避しながら
データベースシステムが高度な並行操作を実行できるヒントの魔法の組み合わせ競合なしを探しているようです。一般に、そのようなことはありません。デッドロックを完全に回避する唯一の方法は、alwaysオブジェクトを同じ順序で変更することです。
impossibleでさえ、これを外部キー関係で実現することは困難です。新しいオブジェクトを挿入するときは、子行の前に親行を追加する必要があることを考慮してください。オブジェクトを削除するときは、親の前に子を削除する必要があります。
これは、慎重なロックが競合状態を防止しないわけではありませんが、すべての状況でdeadlocksを回避することを保証することはできません。
質問に関する限り、yes、UPDLOCK, SERIALIZABLE
ヒントを使用するとUPSERT
競合状態から保護されますが、noはデッドロックを防止しません。それは彼らの頻度を高めることにさえ貢献するかもしれません。これは事前に評価するのが難しいため、経験豊富なSQL Serverデータベースの設計者でさえ、誤解される可能性があります。
複数のオブジェクトが関係するより複雑な要件の場合、一般的な解決策は、自然な順序で最初に子を処理する場合でも、子を変更する前に常に明示的に親行を排他的にロックすることです。
たとえば、注文を削除する場合:
DECLARE @OrderID bigint = 12345;
BEGIN TRANSACTION;
-- EXTRA STEP
-- Dummy update with XLOCK to exclusively lock the parent row
UPDATE TOP (1) dbo.Orders WITH (XLOCK)
SET Lastfiledatetime = NULL
WHERE OrderId = @OrderID;
-- Remove children
DELETE dbo.OrderLineDetail
WHERE OrderId = @OrderID;
-- Remove parent
DELETE dbo.Orders
WHERE OrderId = @OrderID;
COMMIT TRANSACTION;
allの変更が同じ変更順序に従う場合、親の行を排他的にロックする追加のステップは、デッドロックを排除するのに役立ちます。最初に親、次にchild(ren)です。いずれかのプロセスが逆の順序でオブジェクトにアクセスすると、互換性のないロックが原因でデッドロックが発生する可能性があります。
SQL Serverは排他ロック(XLOCK
) 状況によっては を受け入れない場合があるため、親行の何かを実際に変更することが重要になる可能性があることに注意してください。実行されました。
ほぼ同じアイデアの2番目の実装は、Q&A SQL Server内でのアプリケーションロックの実装(分散ロックパターン) のように、アプリケーションロックを使用することです(ドキュメントのリンクを参照)。
これはいくつかの点でより簡単ですが、保護されたオブジェクトを変更するallコードが同じスキームを使用することを確認する必要があります。必要なアプリケーションロックを取得せずに、基になるオブジェクトにアクセスできるようになるとすぐに、スキーム全体が機能しなくなります。
以下の例は、注文の要素を処理する前に、特定の注文番号を排他的にアプリケーションロックする方法を示しています。
BEGIN TRANSACTION;
-- The order number we want exclusive access to
DECLARE @OrderID integer = 12345;
-- Compute the locking resource string
DECLARE @Resource nvarchar(255) = N'dbo.Order' + CONVERT(nvarchar(11), @OrderID);
-- Return code from sys.sp_getapplock
DECLARE @RC integer;
-- Build dynamic SQL
DECLARE @SQL nvarchar(max) =
N'
EXECUTE @RC = sys.sp_getapplock
@Resource = ' + QUOTENAME(@Resource) + N',
@LockMode = Exclusive,
@LockTimeout = -1;'
-- Try to acquire the lock
EXECUTE sys.sp_executesql
@SQL,
N'@RC integer OUTPUT',
@RC = @RC OUTPUT;
-- Do something with the return code value if necessary
SELECT @RC;
--- Sensitive operations go here
-- Release the application lock early if you can
-- using sys.sp_releaseapplock
ROLLBACK TRANSACTION;
並行性は難しいです。申し訳ありません。
そして、ポールホワイトの答えを利用して、幸運にも私はデッドロックの可能性が少なくなります。子Orderlinedetailsは削除しますが、親注文は削除しません。私は常に親注文を挿入/変更してから、子注文明細詳細を削除して挿入します。すべてのトランザクションをこの順序で実行する必要があります。
したがって、以下の最終的な解決策は、すべてを1つのトランザクションに保持することです。
(UPDLOCK、SERIALIZABLE)LOCKは、挿入と変更の両方で排他ロックにエスカレートします(そして、ロックはエスカレートできますが、トランザクションでは決してエスカレートしません)-新しい排他ロックでOrderテーブルを挿入/変更できます
次に、子オーダーラインの詳細を削除して挿入できます(これにより、排他ロックも取得されます)
すべてをこの順序で保持します。トランザクションには排他ロックがあるため、他のすべてのトランザクションは、これが完了するまで切断されます。
BEGIN TRANSACTION;
IF NOT EXISTS
(
SELECT *
FROM Order WITH (UPDLOCK, SERIALIZABLE)
WHERE Order ID=@OrderID
)
INSERT INTO Order () VALUES ()
ELSE
BEGIN
UPDATE Order
SET ...
WHERE OrderID=@OrderID
AND LastFileDatetime<@CreatedTime
END
DELETE OrderLineDetail
WHERE OrderId=@OrderId
AND LastFileDatetime<@CreatedTime;
INSERT INTO OrderLineDetail () VALUES ()
COMMIT TRANSACTION;