繁体   English   中英

SQL Server SP 读取数据时死锁

[英]SQL Server SP Deadlock while reading the data

我们有一个用 C# 编写的带有线程的后端作业,每秒生成一个新线程(不知何故,我们不能增加这个时间。)。 它从数据库读取数据以处理存储过程的帮助并向接口系统发送请求。 目前我们面临着在多个进程中提取相同数据并在我们的日志表中发现死锁的问题。 请建议我们如何刺入锁定,以便只能由单个进程处理相同类型的数据,而其他进程将拥有不同的数据。

DB:SQL Server SP 代码:下面给出

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM