5

我有一个使用 Service Broker 的应用程序是 SQL 2008。大约每天一次,数据库的性能开始受到明显影响,我确定这是因为 Service Broker。如果我使用以下命令硬重置所有代理连接:

ALTER DATABASE [RegencyEnterprise] SET OFFLINE WITH ROLLBACK IMMEDIATE
ALTER DATABASE [RegencyEnterprise] SET ONLINE

然后性能恢复正常,直到大约第二天。我还注意到,当性能很差时,运行以下查询会返回大量(目前大约 1000 个)卡在 STARTED_OUTBOUND 状态的对话:

SELECT * FROM sys.conversation_endpoints

此外,以下查询不会返回其中的任何条目:

SELECT * FROM sys.dm_qn_subscriptions
SELECT * FROM sys.transmission_queue

在此查询返回大量项目的情况下,性能似乎还不错。唯一出现问题的时间是当有 STARTED_OUTBOUND 的连接停留在此状态时。

我对 SQL Server 2008 实例上的 Service Broker 所做的唯一配置是运行以下命令:

ALTER DATABASE RegencyEnterprise SET ENABLE_BROKER

挖掘 SQL 错误日志,我也发现了超过 1000 次这个条目:

07/11/2013 01:00:02,spid27s,Unknown,The query notification dialog on conversation handle '{6DFE46F5-25E9-E211-8DC8-00221994D6E9}.' closed due to the following error: '<?xml version="1.0"?><Error xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/Error"><Code>-8490</Code><Description>Cannot find the remote service &apos;SqlQueryNotificationService-cb4e7a77-58f3-4f93-95c1-261954d3385a&apos; because it does not exist.</Description></Error>'.

我在整个日志中也看到这个错误十几次,但我相信我可以通过在数据库中创建一个主密钥来解决这个问题:

06/26/2013 14:25:01,spid116,Unknown,Service Broker needs to access the master key in the database '<Database name>'. Error code:26. The master key has to exist and the service master key encryption is required.

我认为这些错误的数量可能与停留在队列中的对话数量有关。这是我用来订阅查询通知的 C# 代码:

private void EstablishSqlConnection(
    String storedProcedureName,
    IEnumerable<SqlParameter> parameters,
    Action sqlQueryOperation,
    String serviceCallName,
    Int32 timeout,
    params MultipleResult[] results)
{
    SqlConnection storeConnection = (SqlConnection) ((EntityConnection) ObjectContext.Connection).StoreConnection;
    try
    {
        using (SqlCommand command = storeConnection.CreateCommand())
        {
            command.Connection = storeConnection;
            storeConnection.Open();

            SqlParameter[] sqlParameters = parameters.ToArray();
            command.CommandText = storedProcedureName;
            command.CommandType = CommandType.StoredProcedure;
            command.Parameters.AddRange(sqlParameters);

            if (sqlQueryOperation != null)
            {
                // Register a sql dependency with the SQL query.
                SqlDependency sqlDependency = new SqlDependency(command, null, timeout);
                sqlDependency.OnChange += OnSqlDependencyNotification;
            }

            using (DbDataReader reader = command.ExecuteReader())
            {
                results.ForEach(result => result.MapResults(this, reader));
            }
        }
    }
    finally
    {
        storeConnection.Close();
    }
}

以下是我处理通知的方式:

    public static void OnSqlDependencyNotification(object sender, SqlNotificationEventArgs e)
    {
        if (e.Info == SqlNotificationInfo.Invalid)
        {
            // If we failed to register the SqlDependency, log an error
            <Error is loged here...>

            // If we get here, we are not in a valid state to requeue the sqldependency. However,
            // we are on an async thread and should NOT throw an exception. Instead we just return
            // here, as we have already logged the error to the database. 
            return;
        }

        // If we are able to find and remove the listener, invoke the query operation to re-run the query.
        <Handle notification here...>
    }

有谁知道什么会导致经纪人的连接进入这种状态?或者我可以使用什么工具来试图找出导致这种情况的原因?我目前只有一个 Web 服务器正在注册它的通知,所以我的场景并不太复杂。

更新:

好的,所以我从这篇文章中确定错误“找不到远程服务......因为它不存在”是由于 SqlDependency 没有正确清理自身。服务结束后,代理仍在尝试向我的应用程序发送通知。所以现在,听起来我只需要找到一种方法来清除在调用 SqlDependency.Start() 之前我的应用程序启动时没有正确清理的任何内容,但除了我的原始方法之外,我还没有找到其他方法上面,这会使数据库脱机并且是不可接受的。有谁知道清理这个?

4

3 回答 3

5

我找到了解决此问题的可接受方法。首先,我将我的代码从 SqlDependency 中迁移出来,现在我使用的是 SqlNotificationRequest。这样做可以防止代理队列和服务在意外时间被创建/销毁。

但是,即使这样,当我的应用程序退出时,仍然有一些对话没有被标记为已关闭,因为设置通知的原始端点不再存在。因此,每次我的服务器重新初始化我的代码时,我都会清除现有的对话。

这种调整将我每天拥有的连接数量从超过 1000 个并且不得不手动杀死它们减少到始终最多大约 20 个。我强烈建议使用 SqlNotificationRequest 而不是 SqlDependency。

于 2013-07-15T21:30:26.277 回答
2

我找到了一种方法来清除卡住的对话。我检索仍然存在的所有生成的 SqlDependency 队列,并遍历不属于其中任何一个的对话并结束这些对话。下面是代码:

SET NOCOUNT OFF;
DECLARE @handle UniqueIdentifier
DECLARE @count INT = 0

-- Retrieve orphaned conversation handles that belong to auto-generated SqlDependency queues and iterate over each of them
DECLARE handleCursor CURSOR
FOR 
SELECT [conversation_handle]
FROM sys.conversation_endpoints WITH(NOLOCK)
WHERE
    far_service COLLATE SQL_Latin1_General_CP1_CI_AS like 'SqlQueryNotificationService-%' COLLATE SQL_Latin1_General_CP1_CI_AS AND
    far_service COLLATE SQL_Latin1_General_CP1_CI_AS NOT IN (SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS FROM sys.service_queues)

DECLARE @Rows INT
SELECT @Rows = COUNT(*) FROM sys.conversation_endpoints WITH(NOLOCK)
WHERE
    far_service COLLATE SQL_Latin1_General_CP1_CI_AS like 'SqlQueryNotificationService-%' COLLATE SQL_Latin1_General_CP1_CI_AS AND
    far_service COLLATE SQL_Latin1_General_CP1_CI_AS NOT IN (SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS FROM sys.service_queues)

WHILE @ROWS>0
BEGIN
    OPEN handleCursor

    FETCH NEXT FROM handleCursor 
    INTO @handle

    BEGIN TRANSACTION

    WHILE @@FETCH_STATUS = 0
    BEGIN

        -- End the conversation and clean up any remaining references to it
        END CONVERSATION @handle WITH CLEANUP

        -- Move to the next item
        FETCH NEXT FROM handleCursor INTO @handle
        SET @count= @count+1
    END

    COMMIT TRANSACTION
    print @count

    CLOSE handleCursor;

    IF @count > 100000
    BEGIN
        BREAK;
    END

    SELECT @Rows = COUNT(*) FROM sys.conversation_endpoints WITH(NOLOCK)
    WHERE
        far_service COLLATE SQL_Latin1_General_CP1_CI_AS like 'SqlQueryNotificationService-%' COLLATE SQL_Latin1_General_CP1_CI_AS AND
        far_service COLLATE SQL_Latin1_General_CP1_CI_AS NOT IN (SELECT name COLLATE SQL_Latin1_General_CP1_CI_AS FROM sys.service_queues)
END
DEALLOCATE handleCursor;
于 2013-07-12T20:21:27.037 回答
0

Started Outbound 表示“SQL Server 已为此对话处理了 BEGIN CONVERSATION,但尚未发送任何消息。” (来自 Books Online)看起来您正在创建尚未被使用的对话,因此它们永远不会关闭。

不完全确定为什么这会导致性能下降。

于 2013-07-11T18:23:07.207 回答