5

Service Broker を使用しているアプリケーションは SQL 2008 です。1 日に約 1 回、データベースのパフォーマンスが著しく低下し始めます。これは Service Broker が原因であると判断しました。次のコマンドを使用して、すべてのブローカー接続をハード リセットした場合:

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

その後、パフォーマンスは翌日頃まで通常に戻ります。また、パフォーマンスが低いときに次のクエリを実行すると、STARTED_OUTBOUND 状態でスタックしている多数 (現在約 1000) の会話が返されることにも気付きました。

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 サーバーは 1 つしかないため、シナリオはそれほど複雑ではありません。

アップデート:

わかりましたので、この投稿から、「存在しないため、リモートサービスが見つかりません」というエラーは、SqlDependencyが適切にクリーンアップされていないことが原因であると判断しました。サービスが終了した後も、ブローカーはアプリケーションに通知を送信しようとしています。だから今、SqlDependency.Start() を呼び出す前に、アプリの起動時に適切にクリーンアップされていないものをすべてクリアする方法を見つける必要があるように思えますが、元のメソッド以外にこれを行う方法が見つかりませんでしたこれはデータベースをオフラインにするため、受け入れられません。これをきれいにする方法を知っている人はいますか?

4

3 に答える 3

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 に答える