インデックス付きビューを介してのみ関連する2つのテーブルのデッドロックを解決する


16

デッドロックが発生している状況があり、犯人を絞り込んだと思いますが、それを修正するために何ができるかはよくわかりません。

これは、SQL Server 2008 R2を実行している運用環境です。

状況を少し簡略化して表示するには:


以下に定義する3つのテーブルがあります。

TABLE activity (
    id, -- PK
    ...
)

TABLE member_activity (
    member_id, -- PK col 1
    activity_id, -- PK col 2
    ...
)

TABLE follow (
    id, -- PK
    follower_id,
    member_id,
    ...
)

member_activityテーブルには主キーのように定義する化合物を持っているmember_id, activity_id私は今まで、そのテーブル途中その上でデータを検索する必要があるため、。

また、非クラスタ化インデックスがありfollowます:

CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes] 
ON follow ( member_id ASC ) INCLUDE ( follower_id )

さらに、network_activity次のように定義されたスキーマバインドビューがあります。

CREATE VIEW network_activity
WITH SCHEMABINDING
AS

SELECT
    follow.follower_id as member_id,
    member_activity.activity_id as activity_id,
    COUNT_BIG(*) AS cb
FROM member_activity
INNER JOIN follow ON follow.member_id = member_activity.member_id
INNER JOIN activity ON activity.id = member_activity.activity_id
GROUP BY follow.follower_id, member_activity.activity_id

一意のクラスター化インデックスもあります:

CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id] 
ON network_activity
(
    member_id ASC,
    activity_id ASC
)

現在、2つのデッドロックストアドプロシージャがあります。彼らは次のプロセスを経ます:

-- SP1: insert activity
-----------------------
INSERT INTO activity (...)
SELECT ... FROM member_activity WHERE member_id = @a AND activity_id = @b
INSERT INTO member_activity (...)


-- SP2: insert follow
---------------------
SELECT follow WHERE member_id = @x AND follower_id = @y
INSERT INTO follow (...)

これら2つのプロシージャは、両方ともREAD COMMITTED分離で実行されます。1222拡張イベントの出力をクエリすることができ、デッドロックに関して以下を解釈しました。

SP1はインデックスのRangeS-Sキーロックを待機していますIX_follow_member_id_includesが、SP2は競合する(X)ロックを保持しています

SP1は競合(X)ロックを保持SしているPK_member_activity間、SP2はモードロックを待機しています

デッドロックは、各クエリの最後の行(挿入)で発生しているようです。私にとって不明確なのは、SP1がIX_follow-member_id_includesインデックスのロックを必要としている理由です。私にとって唯一のリンクは、このインデックス付きビューからのものであると思われるため、これを含めました。

これらのデッドロックが発生するのを防ぐ最良の方法は何でしょうか?どんな助けでも大歓迎です。デッドロックの問題を解決した経験はあまりありません。

役立つ情報が他にある場合はお知らせください。

前もって感謝します。


編集1:リクエストごとに情報を追加します。

このデッドロックからの1222出力は次のとおりです。

<deadlock>
    <victim-list>
        <victimProcess id="process4c6672748" />
    </victim-list>
    <process-list>
        <process id="process4c6672748" taskpriority="0" logused="332" waitresource="KEY: 8:72057594104905728 (25014f77eaba)" waittime="581" ownerId="474698706" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.287" XDES="0x298487970" lockMode="RangeS-S" schedulerid="1" kpid="972" status="suspended" spid="79" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T10:25:00.283" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698706" currentdb="8" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
            <executionStack>
                <frame procname="" line="7" stmtstart="1194" stmtend="1434" sqlhandle="0x02000000a26bb72a2b220406876cad09c22242e5265c82e6" />
                <frame procname="" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000" />
            </executionStack>
            <inputbuf> <!-- SP 1 --> </inputbuf>
        </process>
        <process id="process6cddc5b88" taskpriority="0" logused="456" waitresource="KEY: 8:72057594098679808 (89013169fc76)" waittime="567" ownerId="474698698" transactionname="INSERT" lasttranstarted="2014-07-03T17:03:12.283" XDES="0x30c459970" lockMode="S" schedulerid="4" kpid="4204" status="suspended" spid="70" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2014-07-03T17:03:12.283" lastbatchcompleted="2014-07-03T17:03:12.283" lastattention="2014-07-03T15:04:55.870" clientapp=".Net SqlClient Data Provider" hostname="WIN08CLYDESDALE" hostpid="4596" loginname="TechPro" isolationlevel="read committed (2)" xactid="474698698" currentdb="8" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
            <executionStack>
                <frame procname="" line="18" stmtstart="942" stmtend="1250" sqlhandle="0x03000800ca458d315ee9130100a300000100000000000000" />
            </executionStack>
            <inputbuf> <!-- SP 2 --> </inputbuf>
        </process>
    </process-list>
    <resource-list>
        <keylock hobtid="72057594104905728" dbid="8" objectname="" indexname="" id="lock33299fc00" mode="X" associatedObjectId="72057594104905728">
            <owner-list>
                <owner id="process6cddc5b88" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process4c6672748" mode="RangeS-S" requestType="wait" />
            </waiter-list>
        </keylock>
        <keylock hobtid="72057594098679808" dbid="8" objectname="" indexname="" id="lockb7e2ba80" mode="X" associatedObjectId="72057594098679808">
            <owner-list>
                <owner id="process4c6672748" mode="X" />
            </owner-list>
            <waiter-list>
                <waiter id="process6cddc5b88" mode="S" requestType="wait" />
            </waiter-list>
        </keylock>
    </resource-list>
</deadlock>

この場合、

relatedObjectId 72057594098679808に対応 member_activity, PK_member_activity

relatedObjectId 72057594104905728はに対応します follow, IX_follow_member_id_includes

また、SP1とSP2が何をしているのかをより正確に示しています

-- SP1: insert activity
-----------------------
DECLARE @activityId INT

INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)

SET @activityId = SCOPE_IDENTITY();

IF NOT EXISTS(
    SELECT TOP 1 member_id 
    FROM member_activity 
    WHERE member_id = @m1 AND activity_id = @activityId
)
    INSERT INTO member_activity (member_id, activity_id, field1)
    VALUES (@m1, @activityId, @field1)

IF NOT EXISTS(
    SELECT TOP 1 member_id 
    FROM member_activity 
    WHERE member_id = @m2 AND activity_id = @activityId
)
    INSERT INTO member_activity (member_id, activity_id, field1)
    VALUES (@m2, @activityId, @field1)

また、SP2:

-- SP2: insert follow
---------------------

IF NOT EXISTS(
    SELECT TOP 1 1 
    FROM follow
    WHERE member_id = @memberId AND follower_id = @followerId
)
    INSERT INTO follow (member_id, follower_id)
    VALUES (@memberId, @followerId)

編集2:コメントを読み直した後、どの列が外部キーであるかについての情報も追加すると思いました...

  • member_activity.member_idmemberテーブルへの外部キーです
  • member_activity.activity_idactivityテーブルへの外部キーです
  • follow.member_idmemberテーブルへの外部キーです
  • follow.follower_idmemberテーブルへの外部キーです

更新1:

運がなくても、デッドロックの防止に役立つと思われるいくつかの変更を加えました。

私が行った変更は次のとおりです。

-- SP1: insert activity
-----------------------
DECLARE @activityId INT

INSERT INTO activity (field1, field2)
VALUES (@field1, @field2)

SET @activityId = SCOPE_IDENTITY();

MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m1 as member_id, @activityId as activity_id, @field1 as field1) as source
    ON target.member_id = source.member_id
    AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
    INSERT (member_id, activity_id, field1)
    VALUES (source.member_id, source.activity_id, source.field1)
;

MERGE member_activity WITH ( HOLDLOCK ) as target
USING (SELECT @m2 as member_id, @activityId as activity_id, @field1 as field1) as source
    ON target.member_id = source.member_id
    AND target.activity_id = source.activity_id
WHEN NOT MATCHED THEN
    INSERT (member_id, activity_id, field1)
    VALUES (source.member_id, source.activity_id, source.field1)
;

およびSP2の場合:

-- SP2: insert follow
---------------------

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION

IF NOT EXISTS(
    SELECT TOP 1 1 
    FROM follow WITH ( UPDLOCK )
    WHERE member_id = @memberId AND follower_id = @followerId
)
    INSERT INTO follow (member_id, follower_id)
    VALUES (@memberId, @followerId)

COMMIT

これら2つの変更により、まだデッドロックが発生しているようです。

他に提供できるものがあれば、教えてください。ありがとう。


コミットの読み取りはキー範囲ロックを取得せず、シリアル化可能のみを実行します。デッドロックが実際にread committed(2)を示している場合、私の推測では、外部キーを変更することにアクセスしていることになります。さらに正直に言うと、ddlとsp全体が必要です。
ショーンは、サラチップスを削除する14

@SeanGallardy、ありがとう。私が間違って解釈していた場合に備えて1222の出力を含めるように編集し、SPの動作に関する詳細を追加しました。これは役立ちますか?
リーランドリチャードソン14

2
@SeanGallardyインデックス付きビューを維持するクエリプランの一部は、内部で実行されますSERIALIZABLE(それよりも少し多くありますが、これは答えではなくコメントです:)
Paul White Reinstate Monica

@PaulWhite洞察力をありがとう、私はそれを知りませんでした!簡単なテストを行うと、ストアドプロシージャ(RangeI-N、RangeS-S、RangeS-U)への挿入中に、インデックス付きビューでシリアル化可能なロックモードを確実に取得できます。ストアドプロシージャがロック境界内にある場合(たとえば、範囲ロックによって保持されている領域)に挿入するときに、適切なタイミングで相互にヒットする互換性のないロックモードからデッドロックが発生しているようです。タイミングと入力データの衝突の両方が考えられます。
ショーンはサラチップスを削除する14

質問:SELECTステートメントにHOLDLOCKヒントを追加すると、挿入時にロックが発生しなくなりますか?
リーランドリチャードソン14

回答:


4

競合はnetwork_activity、DMLステートメント全体で(内部的に)維持する必要があるインデックス付きビューになります。これは、IX_follow-member_id_includesおそらくビューで使用されるため、SP1がインデックスのロックを必要としている理由です(ビューのカバーインデックスのように見えます)。

2つの可能なオプション:

  1. クラスター化インデックスをビューにドロップして、インデックス付きビューでなくなるようにすることを検討してください。それを持っていることの利点は、保守コストを上回っていますか?頻繁に選択しますか、それともインデックスを作成することによるパフォーマンスの向上に値しますか?これらのprocをかなり頻繁に実行する場合、おそらくコストが利益よりも高いでしょうか?

  2. ビューにインデックスを付ける利点がコストを上回る場合、そのビューのベーステーブルに対してDML操作を分離することを検討してください。これは、アプリケーションロックを使用して実行できます(sp_getapplockおよびsp_releaseapplockを参照)。アプリケーションロックを使用すると、任意の概念を中心にロックを作成できます。@Resourceつまり、両方のストアドプロシージャでを「network_activity」として定義すると、順番を待つように強制できます。各procは同じ構造に従います。

    BEGIN TRANSACTION;
    EXEC sp_getapplock @Resource = 'network_activity', @LockMode = 'Exclusive';
    ...current proc code...
    EXEC sp_releaseapplock @Resource = 'network_activity';
    COMMIT TRANSACTION;

    ROLLBACK(リンクされたMSDNドキュメントに記載されているように)エラーを自分で管理する必要があるので、通常のTRY...CATCH。しかし、これにより状況を管理できます。
    注: sp_getapplock / sp_releaseapplockは控えめに使用する必要があります。アプリケーションロックは非常に便利です(このような場合など)が、絶対に必要な場合にのみ使用してください。


助けてくれてありがとう。オプション#2をもう少し読み上げて、それが機能するかどうかを確認します。ビューはかなりの部分から読み取られ、クラスター化インデックスは非常に大きな助けになります...ですので、まだ削除しないでください。私はこれを試してみると、更新を返します。
リーランドリチャードソン14

sp_getapplockを使用しても機能すると思います。まだ本番環境で試用することはできませんでしたが、賞金が期限切れになる前に賞金を授与されるようにしたかったのです。動作を確認できたら、ここで更新します!
リーランドリチャードソン14

ありがとう。アプリケーションロックの良い点の1つはmember_id@Resource値のように連結する細分性のレベルを変更できることです。これはこの特定の状況には当てはまらないようですが、そのように使用されるのを見てきました。特に、顧客ごとに単一のスレッドにプロセスを限定したいマルチテナントシステムでは非常に便利です。まだ顧客間でマルチスレッド化されています。
ソロモンラッツキー14

更新プログラムを提供し、これ実際に運用環境で機能するようになったと言いたいと思いました。:)
リーランドリチャードソン14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.