Lel*_*son 17 sql-server deadlock materialized-view
我遇到了陷入僵局的情况,我想我已经缩小了罪魁祸首的范围,但我不太确定我能做些什么来解决它。
这是在运行 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,
...
)
Run Code Online (Sandbox Code Playgroud)
该member_activity表有一个定义为 的复合主键member_id, activity_id,因为我只需要以这种方式查找该表上的数据。
我也有一个非聚集索引follow:
CREATE NONCLUSTERED INDEX [IX_follow_member_id_includes]
ON follow ( member_id ASC ) INCLUDE ( follower_id )
Run Code Online (Sandbox Code Playgroud)
此外,我有一个 Schema-bound 视图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
Run Code Online (Sandbox Code Playgroud)
其中还有一个唯一的聚集索引:
CREATE UNIQUE CLUSTERED INDEX [IX_network_activity_unique_member_id_activity_id]
ON network_activity
(
member_id ASC,
activity_id ASC
)
Run Code Online (Sandbox Code Playgroud)
现在,我有两个死锁的存储过程。他们经历以下过程:
-- 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 (...)
Run Code Online (Sandbox Code Playgroud)
这两个过程都在 READ COMMITTED 隔离中运行。我设法查询了 1222 个扩展事件输出,并就死锁解释了以下内容:
SP1 正在等待索引
RangeS-S上的键锁,IX_follow_member_id_includes而 SP2 持有冲突 (X) 锁SP2 正在等待
S模式锁定,PK_member_activity而 SP1 持有冲突 (X) 锁定
死锁似乎发生在每个查询的最后一行(插入)。我不清楚的是为什么 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>
Run Code Online (Sandbox Code Playgroud)
在这种情况下,
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)
Run Code Online (Sandbox Code Playgroud)
还有 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)
Run Code Online (Sandbox Code Playgroud)
编辑 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)
;
Run Code Online (Sandbox Code Playgroud)
和 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
Run Code Online (Sandbox Code Playgroud)
有了这两个变化,我似乎仍然陷入僵局。
如果还有什么我可以提供的,请告诉我。谢谢。
冲突归结为network_activity需要跨 DML 语句(内部)维护的索引视图。这很可能是 SP1 想要锁定IX_follow-member_id_includes索引的原因,因为它可能被视图使用(它看起来是视图的覆盖索引)。
两种可能的选择:
考虑删除视图上的聚集索引,使其不再是索引视图。拥有它的好处是否超过维护成本?您是否足够频繁地从中进行选择,或者将其编入索引所带来的性能提升是否值得?如果您相当频繁地运行这些过程,那么成本可能高于收益?
如果将视图编入索引的好处确实超过了成本,那么请考虑针对该视图的基表隔离 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;
Run Code Online (Sandbox Code Playgroud)
您需要自己管理错误ROLLBACK(如链接的 MSDN 文档中所述),因此请放入通常的TRY...CATCH. 但是,这确实可以让您管理这种情况。
请注意: sp_getapplock /sp_releaseapplock应谨慎使用;应用程序锁绝对可以非常方便(例如在这种情况下),但只有在绝对必要时才应该使用它们。