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