4

我有一个使用Service Broker的应用程序是SQL 2008.大约每天一次数据库的性能开始会显着下降,并且我确定这是因为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实例是运行以下命令:

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服务器,所以我的情况并不太复杂。

UPDATE:

好了,我已经从this post确定错误“无法找到远程服务...因为它不存在”是由于的SqlDependency无法正常后自己清理。该代理仍在尝试在服务结束后向我的应用程序发送通知。所以现在,这听起来像我只需要找到一种方法来清除在调用SqlDependency.Start()之前我的应用程序启动时没有正确清理的任何东西,但是我还没有找到一种方法来做到这一点,而不是我的原始方法上面,它使数据库脱机并且不可接受。有谁知道知道清理这件事?

+0

描述您的Service Broker配置。你有什么队列,你如何使用它们,另一端是哪里?等... – RBarryYoung

+0

我只是使用默认值 - 我还没有创建任何自定义队列。我正在使用我的Web应用程序中的C#SqlDependency对象进行连接。是否有具体的配置信息可以帮助? – lehn0058

+0

您能告诉我们C#代码吗? – Rikalous

回答

4

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

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

这个调整减少了我每天从1000以上的连接数量,并且不得不手动杀死它们,在任何时候都有最多20个连接。我高度推荐使用SqlNotificationRequest而不是SqlDependency。

0

启动出站意味着'SQL Server为此会话处理了BEGIN CONVERSATION,但没有消息尚未发送。' (来自联机丛书) 看起来你正在创建那些没有被使用的会话,所以他们永远不会被关闭。

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

+0

这确实有道理,为什么他们会留在这种状态。这很奇怪,因为我的web服务器只在进程启动时注册对话,并在进程结束时关闭它。我想如果一个未处理的异常被抛出并且IIS重新启动了这个过程,那么会有超过一个对话被启动,并且第一个对话将永远不会被关闭......我同意你的观点,我不希望只有没有被传送的消息影响这样的表现。 – lehn0058

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; 
+1

作为一个方面说明:有一个较低级别的C#API:['SqlNotificaitonRequest'](http://msdn.microsoft.com/en-us/library/system.data.sql.sqlnotificationrequest.aspx)它允许你建立自己的'管道'(目标服务等)。不是微不足道的,但你可以避免'SqlDependency'造成的问题。 –

+0

是的,我确实找到了这门课,我正在考虑使用它。这将允许我定义自己的服务代理对象,而不是在每次建立新连接时自动生成它。 – lehn0058

+0

好的 - 这看起来像是一个定期做的痛苦。标题备份“思想堆栈” - 您是否实施了任何代码来在客户关闭时停用通知? – Rikalous