SQL作业队列实现中的索引与锁定策略

发布于 2024-12-17 12:31:40 字数 2112 浏览 6 评论 0原文

在 SQL 2008+ 中,以下队列表和入队、出队操作旨在允许在任意命名队列中的多个生成器和使用者之间的流中以可选的部分有序连续方式进行高效的作业排队。通过 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 或其他索引可能会对无死锁产生什么负面影响?
  • 还有哪些其他指标是有益的?
  • 表分区(按队列名称)或其他功能如何提高可扩展性?
  • 按照设想,生成器通过外部机制向消费者发出信号,表明数据已排队以避免严格轮询。相反,(在没有 SQL ServiceBroker 的情况下)是否有一种方法可以使用 SQL 锁来有效锁定没有可用行的消费者,直到生成器写入指定队列?

    CREATE INDEX [IDX_Queue_IsFailed_QueueName_DelayUntilUTC] ON [dbo].[Queue] ([IsFailed], [QueueName], [DelayUntilUTC])

另外,我想 OrderBy 并不重要,只要我们总是得到一个没有前驱的订单即可。

In SQL 2008+, the following Queue table and Enqueue, Dequeue operations are intended to allow efficient, job queuing within streams among multiple generators and consumers in arbitrary named queues in an optional partially ordered succession. Trivial support for poison message handling through RetryLater(), FailNow(), Reset() (not shown).

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

Consumer transactions are held open from before Dequeue() through processing application-side and until DELETE, RetryLater(), or FailNow().

  • Is this design sound?
  • Is this setup deadlock free?
  • What negative impact might IDX_Queue_IsFailed_QueueName_DelayUntilUTC or other indexes have on deadlock-freeness?
  • What other indexes are beneficial?
  • How might table partitioning (by queueName) or other features improve the scalability?
  • As conceived, Generators signal to Consumers through an external mechanism that data has been enqueued to avoid tight polling. Instead, (and without SQL ServiceBroker) is there a way to use SQL Locks to efficiently lock a Consumer for which there are no available rows until a generator has written to the named queue?

    CREATE INDEX [IDX_Queue_IsFailed_QueueName_DelayUntilUTC] ON [dbo].[Queue] ([IsFailed], [QueueName], [DelayUntilUTC])

Also, I guess that OrderBy doesn't matter, as long as we always get one with no predecessor.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

仄言 2024-12-24 12:31:40

我对此的 2c 点是:

  • 首先,出队是读取。一定是写的。使用使用 OTUPUT 更新
  • 不要对 DelayUntilUTC 使用 NULL,在插入时使用当前 UTC。 OR 表达式可能会触发扫描,导致死锁,除了性能下降之外,
  • [Queue] 聚集索引必须如下:(IsFailed, QueueName, DelayUntilUTC)
  • 使主键成为非聚集的,或者完全摆脱它,队列中的身份没有太多用处。
  • 摆脱前驱和左连接。你将不得不与他们一起解决僵局,直到退休。

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

我在使用表作为队列。队列必须简单才能扩展。你的设计太花哨了,行不通。

My 2c on this:

  • first and foremost the dequeue is a read. It must be a write. Use UPDATE WITH OTUPUT.
  • don't use NULL for DelayUntilUTC, use current UTC at insert time. OR expressions may trigger scan, resulting in deadlocks, besides the perf degradation
  • the [Queue] clustered index must be as follows: (IsFailed, QueueName, DelayUntilUTC).
  • make the primary key non-clustered, or get rid of it altogether, there is not much use of identities in queues.
  • get rid of the Predecessor and the LEFT JOIN. You'll be cursingly troubleshoot deadlocks until retirement with them.

I understand that the last item is probably very hard to palate, but correlation locking with readpast is basically impossible to achieve on a relational table.

I have talked about this subject at length in Using tables as Queues. Queues must be simple in order to scale. Your design is too fancy and is not going to work.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文