デッドロックが発生している状況で、原因を絞り込んだと思いますが、それを修正するために何ができるのかよくわかりません。
これは、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拡張イベントの出力をクエリして、デッドロックに関して次のことを解釈しました。
SP2が競合する(X)ロックを保持している間、SP1は
RangeS-S
インデックスのIX_follow_member_id_includes
キーロックを待機していますSP1が競合する(X)ロックを保持している間、SP2は
PK_member_activity
のS
モードロックを待機しています
デッドロックは、各クエリ(挿入)の最後の行で発生しているようです。 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>
この場合、
associatedObjectId 72057594098679808はmember_activity, PK_member_activity
に対応します
associatedObjectId 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_id
はmember
テーブルへの外部キーですmember_activity.activity_id
はactivity
テーブルへの外部キーですfollow.member_id
はmember
テーブルへの外部キーですfollow.follower_id
はmember
テーブルへの外部キーです更新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つの変更を行っても、デッドロックが発生しているようです。
他に提供できることがありましたら、お知らせください。ありがとう。
紛争はnetwork_activity
インデックス付きビューであり、DMLステートメント全体で(内部で)維持する必要があります。これが、SP1がIX_follow-member_id_includes
インデックス。ビューで使用されている可能性があります(ビューのカバリングインデックスのようです)。
2つの可能なオプション:
クラスター化インデックスをビューにドロップして、インデックス付きビューでなくなるようにすることを検討してください。維持費を上回るメリットはありますか?それから十分な頻度で選択しますか、それともそれをインデックス化することでパフォーマンスが向上しますか?これらのプロシージャをかなり頻繁に実行すると、コストがメリットよりも高くなる可能性がありますか?
ビューにインデックスを付ける利点がコストを上回る場合は、そのビューのベーステーブルに対してDML操作を分離することを検討してください。これは、アプリケーションロックを使用して実行できます( sp_getapplock および sp_releaseapplock を参照)。アプリケーションロックを使用すると、任意の概念を中心にロックを作成できます。つまり、@Resource
両方のストアドプロシージャの "network_activity"として、強制的に順番を待つようにします。各プロシージャは同じ構造に従います。
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
は慎重に使用してください。アプリケーションロックは非常に便利です(このような場合など)が、絶対に必要な場合にのみ使用してください。