0

在 SQL 2008+ 中,以下 Queue table 和 Enqueue、Dequeue 操作旨在允许在任意命名队列中的多个生成器和消费者之间的流中以可选的部分顺序连续进行高效的作业排队。通过 RetryLater()、FailNow()、Reset()(未显示)对有害消息处理的微不足道的支持。

CREATE TABLE [dbo].[Queue](
    [ID] BIGINT IDENTITY(1,1) NOT NULL CONSTRAINT [PK_Queue] PRIMARY KEY,
    [PredecessorID] BIGINT NULL,
    [QueueName] NVARCHAR(255) NOT NULL,
    [DataType] NVARCHAR(255) NOT NULL,
    [Data] NVARCHAR(MAX) NOT NULL,
    [RetriesRemaining] INT CONSTRAINT [CHK_RetryCount] CHECK ([RetriesRemaining] IS NULL OR 0 <= [RetriesRemaining]),
    [IsFailed] AS (CASE WHEN [RetriesRemaining] IS NULL OR 0 < [RetriesRemaining] THEN 0 ELSE 1 END) PERSISTED,
    [QueuedOnUTC] DATETIME NOT NULL DEFAULT (GETUTCDATE()),
    [DelayUntilUTC] DATETIME NULL,
    [LastFailedOnUTC] DATETIME NULL,
    [LastFailure] NVARCHAR(MAX)
)

-- Enqueue
INSERT INTO [dbo].[Queue]
(
    [PredecessorID],
    [QueueName],
    [DataType],
    [Data],
    [RetriesRemaining]
)
VALUES
(
    @PredecessorID,
    @QueueName,
    @DataType,
    @Data,
    @RetriesRemaining
)

-- Dequeue
SELECT
    [Dequeued].[ID],
    [Dequeued].[QueueName],
    [Dequeued].[DataType],
    [Dequeued].[Data]
FROM
    [dbo].[Queue] AS [Dequeued] WITH (ROWLOCK, UPDLOCK, READPAST)
LEFT JOIN
    [dbo].[Queue] AS [Predecessor] ON
    [Dequeued].[PredecessorID] = [Predecessor].[ID]
WHERE
    [Dequeued].[IsFailed] = 0
    AND [Dequeued].[QueueName] = @QueueName
    AND ([Dequeued].[DelayUntilUTC] IS NULL OR [Dequeued].[DelayUntilUTC] < GETUTCDATE())
    AND [Predecessor].[ID] IS NULL

消费者事务从 Dequeue() 之前到处理应用程序端一直保持打开状态,直到 DELETE、RetryLater() 或 FailNow()。

  • 这个设计好听吗?
  • 这个设置死锁是免费的吗?
  • IDX_Queue_IsFailed_QueueName_DelayUntilUTC 或其他索引可能对无死锁产生什么负面影响?
  • 还有哪些指标是有益的?
  • 表分区(按 queueName)或其他功能如何提高可伸缩性?
  • 正如设想的那样,生成器通过外部机制向消费者发出信号,表明数据已排队以避免紧密轮询。相反,(并且没有 SQL ServiceBroker)有没有办法使用 SQL 锁来有效地锁定在生成器写入命名队列之前没有可用行的消费者?

    创建索引 [IDX_Queue_IsFailed_QueueName_DelayUntilUTC] ON [dbo].[Queue] ([IsFailed], [QueueName], [DelayUntilUTC])

另外,我想 OrderBy 无关紧要,只要我们总是得到一个没有前任的。

4

1 回答 1

2

我对此的2c:

  • 首先,出队是读取。应该是写的。使用UPDATE WITH OTUPUT.
  • 不要对 DelayUntilUTC 使用 NULL,在插入时使用当前 UTC。OR 表达式可能会触发扫描,导致死锁,除了性能下降
  • [Queue]聚集索引必须如下:(IsFailed, QueueName, DelayUntilUTC).
  • 使主键不聚集,或者完全摆脱它,队列中没有太多使用身份。
  • 摆脱 Predecessor 和 LEFT JOIN。在与他们一起退休之前,您将被诅咒地解决死锁问题。

我知道最后一项可能很难上口,但是在关系表上基本上不可能实现使用 readpast 进行相关锁定。

我在Using tables as Queues中详细讨论了这个主题。队列必须简单才能扩展。你的设计太花哨了,行不通。

于 2011-11-22T21:24:42.293 回答