数据库阻止后,​​SQL Service Broker严重错误接收消息

时间:2018-01-16 19:46:02

标签: sql .net sql-server f# service-broker

我们使用SQL Service Broker队列来通知我们的应用程序新记录,这些记录满足添加到另一个应用程序数据库中的表的某些条件。这是通过让after insert触发器针对for xml虚拟表运行inserted查询并将任何结果插入特定服务代理队列来实现的。然后,我们有一个Notifier对象从服务代理队列接收,并为收到的每条消息调用回调。我们从Service Broker队列接收的代码如下:

let receiveXmlMessage connection transaction (cancellation: CancellationToken) queueName messageTypeName =
    task {
        let commandTimeout = if cancellation.IsCancellationRequested then 1 else 0 
        let receiveQuery = 
            sprintf """WAITFOR
                        (
                            RECEIVE TOP(1)
                                @message     = CONVERT(xml, message_body),
                                @messageType = message_type_name,
                                @dialogId    = conversation_handle
                            FROM dbo.[%s]
                        ), TIMEOUT 60000;""" (sanitize queueName)
        use receiveCommand = 
            match transaction with
            | Some tx -> new SqlCommand(receiveQuery, connection, tx, CommandTimeout = commandTimeout)
            | None -> new SqlCommand(receiveQuery, connection, CommandTimeout = commandTimeout)
        receiveCommand.Parameters.AddRange([| SqlParameter("@message", SqlDbType.Xml, Direction = ParameterDirection.Output); 
                                              SqlParameter("@messageType", SqlDbType.NVarChar, Direction = ParameterDirection.Output, Size = 256); 
                                              SqlParameter("@dialogId", SqlDbType.UniqueIdentifier, Direction = ParameterDirection.Output); |])
        try
            let! receiveResult = receiveCommand.ExecuteNonQueryAsync(if commandTimeout = 0 then cancellation else CancellationToken.None)
            if receiveResult > 0
            then let messageType = receiveCommand.Parameters.["@messageType"].Value |> unbox<string>
                 let dialogId = receiveCommand.Parameters.["@dialogId"].Value |> unbox<Guid>
                 if messageType = messageTypeName
                 then do! endConversation connection transaction dialogId
                      return receiveCommand.Parameters.["@message"].Value |> unbox<string> |> XDocument.Parse 
                 else return XDocument()
            else return XDocument()
        with | ex -> 
            log.errorxf ex "Failed to receive message from Service Broker Queue %s" queueName
            return! Task.FromException ex
    }

这几个月工作正常,处理数百万条消息,直到几天前,当我们有另一个进程导致我们监视的数据库被广泛阻塞时,我们的DBA必须终止几个数据库会话以减轻争用。自从此事件发生以来,我们的应用程序在尝试从Service Broker队列接收时遇到以下错误:

2018-01-11 07:50:27.183-05:00 [31] ERROR - Failed to receive message from Service Broker Queue Notifier_Queue
System.Data.SqlClient.SqlException (0x80131904): A severe error occurred on the current command.  The results, if any, should be discarded.
Operation cancelled by user.
   at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
   at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
   at System.Data.SqlClient.SqlCommand.CompleteAsyncExecuteReader()
   at System.Data.SqlClient.SqlCommand.EndExecuteNonQueryInternal(IAsyncResult asyncResult)
   at System.Data.SqlClient.SqlCommand.EndExecuteNonQueryAsync(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at Application.Common.Sql.ServiceBroker.receiveXmlMessage@257-3.Invoke(Unit unitVar0)
   at Application.Common.TaskBuilder.tryWith[a](FSharpFunc`2 step, FSharpFunc`2 catch)

新消息已成功添加到队列中,我们可以使用SSMS从同一队列接收消息,甚至可以作为与应用程序相同的用户运行的F#交互式会话。它似乎只是我们的应用程序受到影响,但它确实会影响我们的应用程序的所有实例,在不同的服务器上,只要它们连接到此特定数据库。我们已经尝试重新启动应用程序和SQL Server,我们尝试运行ALTER DATABASE ... SET NEW_BROKER WITH ROLLBACK IMMEDIATE。我们尝试过的任何内容都没有带来任何影响,我们最终仍然遇到相同的异常,并且我们仍有数十万个会话保持CONVERSING状态,因为我们调用END CONVERSATION的代码仅在调用之后调用成功接收消息。

我们的SQL Service Broker队列设置为模仿独白模式,如this blog post中所述。

我们如何诊断我们的应用程序从SQL Server返回的这种非特定异常的原因?当问题首次发生时,还有什么我们可以尝试诊断和/或纠正我们的应用程序和SQL Service Broker之间发生的任何变化吗?

1 个答案:

答案 0 :(得分:0)

当我们尝试从Service Broker队列接收时,我们终于找到了导致此错误的原因。事实证明,传递给我们的CancellationToken函数的receiveXmlMessage被我们的应用程序中的其他逻辑取消,该逻辑监视conversing次会话的次数并尝试重新创建我们的Notifier conversing 1}}如果conversing个会话的数量超过某个阈值且最近关闭的会话超过另一个阈值,则为对象。由于最近关闭的会话时代的逻辑错误,实际上只有Notifier个会话的数量被用于重置conversing,并且当上周发生数据库阻止时,累积了150,000次CancellationToken个对话。这导致我们的应用程序在我们尝试从Service Broker接收消息时不断取消conversing。一旦我们关闭了应用程序,清理了所有A severe error occurred on the current command. The results, if any, should be discarded. 个对话,并修复了上次关闭对话的日期数学中的错误,错误就停止了。

对于遇到该消息的任何人,可能需要注意:

CancellationToken

这可能是ExecuteNonQueryAsync传递给ExecuteReaderAsync上的ExecuteScalarAsync / ExecuteXmlReaderAsync / SqlCommand / Commit A -> Create file with code Commit B -> Add file header 方法的结果在方法执行时被取消。