回滚然后从队列中接收相同的消息

Rollback then receive the same message from queue

我有一个这样的激活存储过程:

DECLARE
    @conversation_handle [uniqueidentifier],
    @message_body [varbinary](max)

WHILE 1 = 1
BEGIN TRY
    BEGIN TRANSACTION

    WAITFOR
    (
        RECEIVE TOP (1)
            @conversation_handle = [conversation_handle],
            @message_body = [message_body]
        FROM
            [dbo].[my_queue]
    ), TIMEOUT 1000;

    IF @@ROWCOUNT = 0
    BEGIN
        ROLLBACK TRANSACTION
        BREAK
    END
    
    SAVE TRANSACTION SavePoint
    -- do things
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF XACT_STATE() = 1
        ROLLBACK TRANSACTION SavePoint

    IF XACT_STATE() = -1
    BEGIN
        ROLLBACK TRANSACTION

        BEGIN TRANSACTION;

        RECEIVE TOP (1)
            @message_body = [message_body]
        FROM
            [dbo].[my_queue]
        WHERE
            conversation_handle = @conversation_handle
    END

    -- insert the message to a error log table

    END CONVERSATION @conversation_handle

    COMMIT TRANSACTION
END CATCH

我的问题:如果这个队列有多个队列readers,在队列readerA回滚事务后,另一个队列readerB是否会收到毒消息,并且reader 鉴于所有消息都有自己的会话组,A 无法接收具有会话句柄的相同消息?

will another queue reader B receive the poison message after queue reader A has rolled the transaction back

是的。如果您回滚消息在队列中可用,等待另一个 reader 接收,并且您的会话组锁定被释放。

因此,如果您将一条消息从队列中取出并以注定失败的事务结束,或者 XACT_ABORT 开启,您别无选择,只能完全回滚。再加上与 MultipleActiveResultSets 的不兼容,保存点真的没那么有用。

那怎么办?一种选择是在队列上启用消息保留并提交 RECEIVE。然后在出错时写入您的错误 table。消息正文将在错误 table 和 SELECT 的对话生命周期中从队列中提供。

或者您可以将 MAX_QUEUE_READERS 设置为 1 以防止并发激活过程使毒消息出列。

或者(未经测试)您可以回滚,并在您的会话拥有独占 Application Lock 时重新接收毒消息,并要求激活过程的初始接收在同一资源名称上持有共享锁。