[英]SQL Server SP Deadlock while reading the data
我们有一个用 C# 编写的带有线程的后端作业,每秒生成一个新线程(不知何故,我们不能增加这个时间。)。 它从数据库读取数据以处理存储过程的帮助并向接口系统发送请求。 目前我们面临着在多个进程中提取相同数据并在我们的日志表中发现死锁的问题。 请建议我们如何刺入锁定,以便只能由单个进程处理相同类型的数据,而其他进程将拥有不同的数据。
ALTER PROCEDURE [Migration]
AS
BEGIN
declare @ConversationID varchar(200)='',Group varchar(100) =''
-- Select records with Flag 0
select
top 1 @ConversationID = ConversationID,
@Group = Group
from [Migration]
where NewCode = (select top 1 NewCode
from [Migration]
where Flag = 0 group by NewCode, Group, InsertDate
)
and Flag = 0;
select * from [Migration] where ConversationID = @ConversationID and Group = @Group;
BEGIN TRANSACTION
BEGIN TRY
update [Migration] set Flag = 1 where ConversationID = @ConversationID and Group = @Group and
Flag = 0;
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
insert into Logs(ErrorType,Description) values('MigrationError',ERROR_MESSAGE());
END CATCH
END
典型的死锁消息类似于
事务(进程 ID 100)在锁定资源上与另一个进程发生死锁,并已被选为死锁牺牲品。 重新运行事务。
但是,很难看出您的查询会在哪里以传统方式死锁 - 我可以看到它被阻塞,但没有死锁,因为您通常需要在同一个事务中发生多个更新。 事实上,你上面的事务只是一个命令——围绕它的事务是相当没有意义的,因为每个命令无论如何都在它自己的隐式事务中运行。
相反,我猜你的死锁消息是这样的
事务(进程 ID 100)因锁定而死锁 | 与另一个进程的通信缓冲区资源并已被选为死锁受害者。 重新运行事务。
请注意死锁的不同之处 - 在这种情况下,是锁定/通信缓冲区资源。
这些死锁与并行问题有关; 请参阅(例如) “锁定 | 通信缓冲区资源”是什么意思? 想要查询更多的信息。
如果 SP 执行并行处理并且您连续多次运行此 SP,您将倾向于获得此信息。 设置 MAXDOP 1 和/或改进索引通常可以解决此类问题 - 但显然您需要在自己的特定情况下研究最佳方法。
关于问题本身 - 如何使一次只有一件事可以处理给定的行?
有很多方法。 我常用的方法涉及带有OUTPUT
子句的语句。 这样做的好处是您可以执行数据插入/更新以及记录您在同一命令中插入或更新的内容。
这是您当前事物的一个示例 - 它为它正在处理的行将标志设置为 -1。
CREATE TABLE #ActiveMigrations (ConversationID varchar(200), CGroup varchar(100));
-- Select records with Flag 0
WITH TopGroup AS (
select top 1 ConversationID, CGroup, Flag
from [Migration]
where NewCode = (select top 1 NewCode
from [Migration]
where Flag = 0 group by NewCode, CGroup, InsertDate
)
and Flag = 0
)
UPDATE TopGroup
SET Flag = -1
OUTPUT ConversationID, CGroup
INTO #ActiveMigrations (ConversationID, CGroup)
FROM TopGroup;
在上面,您在临时表(而不是变量)中有 ConversaionID 和 Group 用于进一步处理,并且标志设置为 -1,因此它们不会被进一步处理选中。
类似的版本可用于跟踪(在单独的表中)正在操作的对象。 在这种情况下,您创建一个包含活动对话/组的临时表,例如,
-- Before you switch to the new process, create the table as below
CREATE TABLE ActiveMigrations (ConversationID varchar(200), CGroup varchar(100));
您可以使用上面的 OUTPUT 子句对此进行编码,并且(例如)将此表作为 LEFT OUTER JOIN 包含在您的初始 SELECT 中以排除任何活动行。
一个更简单的版本是使用上面的 SELECT 来获取 @ConversationID 和 @CGroup,然后尝试将其插入表
INSERT INTO ActiveMigrations (ConversationID, CGroup)
SELECT @ConversationID, @Group
WHERE NOT EXISTS
(SELECT 1 FROM ActiveMigrations WHERE ConversationID = @ConversationID AND CGroup = @Group);
IF @@ROWCOUNT = 1
BEGIN
...
考虑这些的关键是,如果你让命令同时运行两次,它们怎么会交互不好?
老实说,你的代码本身
update [Migration]
set Flag = 1
where ConversationID = @ConversationID
and Group = @Group
and Flag = 0;
保护自己,因为 a) 它只是一个命令,并且 b) 它最后有 Flag=0 保护。
它确实浪费了资源,因为第二个并发进程完成了所有工作,然后到了最后,因为第一个进程已经更新了该行而无所事事。 对于定期运行并且可能存在并发问题的东西,那么您可能应该更好地对其进行编码。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.