【问题标题】:How can I avoid or minimize deadlocks in this situation?在这种情况下如何避免或减少死锁?
【发布时间】:2015-04-16 04:23:22
【问题描述】:

我有一张相对较小的桌子(目前)。它就像一个花哨的队列。每/秒/执行一次的作业,向该表询问更多工作,并且每当工作完成时,它们会告诉该表工作已完成。

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

Job Starter 执行一个从该表请求工作的存储过程。基本上,proc 会查看表格,查看哪些任务未进行和过期,将它们标记为“进行中”并将它们返回给 job starter。

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

问题:当我要求此表进行更多工作或尝试将条目标记为已完成时,我经常遇到死锁。看起来 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. 是否存在索引会影响 ROWLOCK 的行为(如果有索引,可能会锁定索引,而不是行)。 2. 您的事务隔离级别可能会影响结果。 3. 您可能想使用 UPDLOCK,而不是 ROWLOCK(四处搜索,在某些情况下它似乎有助于防止死锁 - 就我而言,出乎意料)。

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


【解决方案1】:

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

HTH,

-托比亚斯

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

【讨论】:

  • 谢谢!我刚刚在这里看到了类似的建议rusanu.com/2010/03/26/using-tables-as-queues我需要在桌子上做任何特殊的索引吗?
  • 在这种情况下,为了避免锁定,您可能希望在您过滤的列上添加索引,即 IsEnabledm ActivityType 和 DataCenter。您还可以在 PK 中交换 ActivityType 和 ItemID 的顺序。我建议您阅读更多关于 SQL Server 中的索引的内容,您会发现大量有价值的信息 :-) 您可以从这里开始:msdn.microsoft.com/en-us/library/ms175049.aspx
  • 谢谢托拜厄斯。只是 CTE 的改进帮助了 /ton/。没有更多的死锁,一切都处理得非常顺利。感谢有关索引的建议,当表变大时会有所帮助。
猜你喜欢
  • 2015-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-21
  • 1970-01-01
相关资源
最近更新 更多