2つのテーブルがある場合
親
_KeyID GroupID Name Active
_
子
_KeyID ParentID Name
_
_Child.ParentID
_は_Parent.KeyID
_にFKされます
Parent
とChild
の両方を1つのトランザクションに挿入します。
トランザクションがアクティブなときに別のParent
行が更新された場合(例:Active
1-> 0)、Child
INSERT
は次のエラーで失敗します:
更新の競合により、スナップショット分離トランザクションが中止されました。スナップショット分離を使用して、データベース 'Test'のテーブル 'dbo.Child'に直接または間接的にアクセスして、別のトランザクションによって変更または削除された行を更新、削除、または挿入することはできません。トランザクションを再試行するか、更新/削除ステートメントの分離レベルを変更してください。
何がわかるか 「更新の競合によりスナップショット分離トランザクションが中止されたのはなぜですか?」 これはおそらく、外部キーを検証するためのフルスキャンが原因です。
実際、外部キーを削除すると、Child
INSERT
が期待どおりに完了するようになります。
そうは言っても、Child
テーブルの外部キーにある非クラスター化インデックスの量がこの問題の解決に役立っていないように見えるので、何をすべきか途方に暮れています。
このデータベースに対してRCSIをオンにしており、トランザクションはスナップショット分離モードで実行されています。
追加の詳細
Childへの挿入が指定された行数よりも大きい場合に、この問題が発生することを発見しました。この時点で、クエリオプティマイザーはNested Loops (Left Semi Join)
からMerge Join (Left Semi Join)
に切り替わります。
単一の親レコードに複数の子レコードが挿入されているという事実を含まないことの謝罪。
Insert sprocは大体これです:
_CREATE PROCEDURE dbo.[usp_InsertRecords] (
@journal dbo.ParentType READONLY,
@journalItems dbo.ChildType READONLY,
@tenantId INT
) AS
BEGIN
INSERT INTO dbo.Parent(GroupID, Name, Active, TenantId)
SELECT GroupID, Name, Active, @tenantId FROM @journal
DECLARE @JournalId INT = convert(int,scope_identity());
INSERT INTO dbo.Child(ParentID, Name, TenantId)
SELECT @JournalId, Name, @tenantId
FROM @journalItems j2
END
GO
_
また、同時更新は次のようになります。
_UPDATE dbo.Parent Set Active = 0 WHERE KeyID = 1234 -- row not being inserted
_
INSERT
ステートメントにOPTION (LOOP JOIN)
ヒントを追加します。
または、プランガイド(またはクエリストア)を使用して、ネストされたループの準結合プラン形状を強制します。
OPTION (FAST 1)
も機能する場合があります。
重要なのは、マージされたセミ結合を回避することです。この場合、参照されるテーブルの行の多く(潜在的にはすべて)が現在のトランザクションに影響されます。変更(作成を含む)を持つ親行が検出されると、 更新競合エラーが発生します 。