在这种情况下,如何避免或最大限度地减少死锁?

时间:2015-04-16 04:23:23

标签: sql-server indexing azure-sql-database deadlock

我有一个相对较小的桌子(现在)。它可以作为一个奇特的队列。每隔/秒执行一次的作业/,要求此表进行更多工作,并且每当工作完成时,他们会告诉表格工作已完成。

表有~1000个条目左右的条目,长期有希望有100k +行 每个条目表示需要每分钟执行一次的作业。表托管在 SQL Azure (S2计划)

Job Starter执行从该表请求工作的存储过程。基本上,proc查看表,查看哪些任务没有进行并且已经过期,将它们标记为“正在进行中”并将它们返回给作业启动器。

当任务完成时,执行一个简单的更新以告知该任务已完成,并且可以在一分钟内进行另一个工作循环(字段称为频率控制此)

问题:当我要求此表进行更多工作或尝试将条目标记为已完成时,我经常会遇到死锁。看起来像ROWLOCK提示不起作用。我需要在此表上建立索引结构吗?

这是一个检索记录的存储过程(通常最多20个,由@count参数控制

CREATE PROCEDURE [dbo].[sp_GetScheduledItems]
@activity NVARCHAR (50), @count INT, @timeout INT=300, @dataCenter NVARCHAR (50)
AS
BEGIN
 SET NOCOUNT ON;
 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

 DECLARE @batchId uniqueidentifier
 SELECT @batchId = NEWID()

 DECLARE @result int;
 DECLARE @process nvarchar(255);

   BEGIN TRAN
   -- Update rows
   UPDATE Schedule 
   WITH (ROWLOCK)
   SET 
    LastBatchId = @batchId, 
    LastStartedProcessingId = NEWID(), 
    LastStartedProcessingTime = GETUTCDATE()
   WHERE 
    ActivityType = @activity AND 
    IsEnabled = 1 AND
    ItemId IN (
     SELECT TOP (@count) ItemId 
     FROM Schedule 
     WHERE 
      (LastStartedProcessingId = LastCompletedProcessingId OR LastCompletedProcessingId IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > @timeout) AND 
      IsEnabled = 1 AND ActivityType = @activity AND DataCenter = @dataCenter AND 
      (LastStartedProcessingTime IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > Frequency)
     ORDER BY (DATEDIFF(SECOND, ISNULL(LastStartedProcessingTime, '1/1/2000'), GETUTCDATE()) - Frequency) DESC
    ) 

   COMMIT TRAN

   -- Return the updated rows
   SELECT ItemId, ParentItemId, ItemName, ParentItemName, DataCenter, LastStartedProcessingId, Frequency, LastProcessTime, ActivityType
   FROM Schedule 
   WHERE LastBatchId = @batchId

END
GO

这是一个存储过程,用于将条目标记为已完成(它一次一个地执行)

CREATE PROCEDURE [dbo].[sp_CompleteScheduledItem]
@activity NVARCHAR (50), @itemId UNIQUEIDENTIFIER, @processingId UNIQUEIDENTIFIER, @status NVARCHAR (50), @lastProcessTime DATETIME, @dataCenter NVARCHAR (50)
AS
BEGIN
 -- SET NOCOUNT ON added to prevent extra result sets from
 -- interfering with SELECT statements.
 SET NOCOUNT ON;
 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

 UPDATE Schedule WITH (ROWLOCK)
 SET
  LastCompletedProcessingId = LastStartedProcessingId,
  LastCompletedProcessingTime = GETUTCDATE(),
  LastCompletedStatus = @status,
  LastProcessTime = @lastProcessTime
 WHERE
  ItemId = @itemId AND
  LastStartedProcessingId = @processingId AND
  DataCenter = @dataCenter AND
  ActivityType = @activity
END
GO

这是表格本身

CREATE TABLE [dbo].[Schedule](
    [ItemId] [uniqueidentifier] NOT NULL,
    [ParentItemId] [uniqueidentifier] NOT NULL,
    [ActivityType] [nvarchar](50) NOT NULL,
    [Frequency] [int] NOT NULL,
    [LastBatchId] [uniqueidentifier] NULL,
    [LastStartedProcessingId] [uniqueidentifier] NULL,
    [LastStartedProcessingTime] [datetime] NULL,
    [LastCompletedProcessingId] [uniqueidentifier] NULL,
    [LastCompletedProcessingTime] [datetime] NULL,
    [LastCompletedStatus] [nvarchar](50) NULL,
    [IsEnabled] [bit] NOT NULL,
    [LastProcessTime] [datetime] NULL,
    [DataCenter] [nvarchar](50) NOT NULL,
    [ItemName] [nvarchar](255) NOT NULL,
    [ParentItemName] [nvarchar](255) NOT NULL,
 CONSTRAINT [PK_Schedule] PRIMARY KEY CLUSTERED 
(
    [DataCenter] ASC,
    [ItemId] ASC,
    [ActivityType] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON)
)

1 个答案:

答案 0 :(得分:2)

这是一个很好的问题:-)像往常一样,你可以做很多事情,但在你的情况下,我认为我们可以简化你的查询。请注意,下面的建议并没有使用SERIALIZABLE隔离级别,在您的情况下,很可能导致表级锁定以防止幻像读取发生(并且还可以对您的表进行所有写入访问,很好,序列化。您也可以实际上需要指定BEGIN& COMMIT TRAN,因为您只在事务中发出一条语句(尽管在您的情况下它也不会受到伤害)。在这个例子中,我们利用了我们实际可以发出的事实你的更新直接针对子查询(在这种情况下是CTE的形式),我们也可以删除你的最后一个SELECT,因为我们可以直接从UPDATE语句返回结果集。

HTH,

-Tobias

SQL Server团队

CREATE PROCEDURE [dbo].[sp_GetScheduledItems] 
@activity NVARCHAR (50), @count INT, @timeout INT=300, @dataCenter NVARCHAR (50)
AS
BEGIN
  SET NOCOUNT ON;
  DECLARE @batchId uniqueidentifier

  SELECT @batchId = NEWID()
  DECLARE @result int;
  DECLARE @process nvarchar(255);

  -- Update rows
  WITH a AS (
    SELECT TOP (@count) 
        *
    FROM Schedule 
    WHERE 
    (LastStartedProcessingId = LastCompletedProcessingId OR LastCompletedProcessingId IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > @timeout) AND 
    IsEnabled = 1 AND ActivityType = @activity AND DataCenter = @dataCenter AND 
    (LastStartedProcessingTime IS NULL OR DATEDIFF(SECOND, LastStartedProcessingTime, GETUTCDATE()) > Frequency)
    ORDER BY (DATEDIFF(SECOND, ISNULL(LastStartedProcessingTime, '1/1/2000'), GETUTCDATE()) - Frequency) DESC
  )
  UPDATE a SET 
    LastBatchId = @batchId, 
    LastStartedProcessingId = NEWID(), 
    LastStartedProcessingTime = GETUTCDATE()
  OUTPUT INSERTED.ItemId, INSERTED.ParentItemId, INSERTED.ItemName,   INSERTED.ParentItemName, INSERTED.DataCenter, INSERTED.LastStartedProcessingId,   INSERTED.Frequency, INSERTED.LastProcessTime, INSERTED.ActivityType

END