web-dev-qa-db-ja.com

INSERTでスナップショット分離の問題が発生するのはなぜですか?

2つのテーブルがある場合

_KeyID   GroupID   Name  Active
_

_KeyID   ParentID  Name
_

_Child.ParentID_は_Parent.KeyID_にFKされます

ParentChildの両方を1つのトランザクションに挿入します。

トランザクションがアクティブなときに別のParent行が更新された場合(例:Active 1-> 0)、ChildINSERTは次のエラーで失敗します:

更新の競合により、スナップショット分離トランザクションが中止されました。スナップショット分離を使用して、データベース 'Test'のテーブル 'dbo.Child'に直接または間接的にアクセスして、別のトランザクションによって変更または削除された行を更新、削除、または挿入することはできません。トランザクションを再試行するか、更新/削除ステートメントの分離レベルを変更してください。

何がわかるか 「更新の競合によりスナップショット分離トランザクションが中止されたのはなぜですか?」 これはおそらく、外部キーを検証するためのフルスキャンが原因です。

実際、外部キーを削除すると、ChildINSERTが期待どおりに完了するようになります。

そうは言っても、Childテーブルの外部キーにある非クラスター化インデックスの量がこの問題の解決に役立っていないように見えるので、何をすべきか途方に暮れています。

このデータベースに対してRCSIをオンにしており、トランザクションはスナップショット分離モードで実行されています。

追加の詳細

Childへの挿入が指定された行数よりも大きい場合に、この問題が発生することを発見しました。この時点で、クエリオプティマイザーはNested Loops (Left Semi Join)からMerge Join (Left Semi Join)に切り替わります。

単一の親レコードに複数の子レコードが挿入されているという事実を含まないことの謝罪。

作業挿入(20の子レコード): Working insert

挿入の失敗(50の子レコード): Failing insert

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
_
11
joshschreuder

INSERTステートメントにOPTION (LOOP JOIN)ヒントを追加します。

または、プランガイド(またはクエリストア)を使用して、ネストされたループの準結合プラン形状を強制します。

OPTION (FAST 1)も機能する場合があります。

重要なのは、マージされたセミ結合を回避することです。この場合、参照されるテーブルの行の多く(潜在的にはすべて)が現在のトランザクションに影響されます。変更(作成を含む)を持つ親行が検出されると、 更新競合エラーが発生します

6
Paul White 9