【问题标题】:SqlDependency subscription not dropped from dm_qn_subscriptions on shutdown关闭时未从 dm_qn_subscriptions 删除 SqlDependency 订阅
【发布时间】:2013-04-07 15:26:40
【问题描述】:

我的 SqlDependency 工作正常,当应用程序退出时,代理队列和服务会正确删除(我确实按照之前的建议执行 SqlDependency.Stop(...)终止进程),但我注意到由 SqlDependency 创建的通知订阅在应用程序关闭后仍然存在于表“sys.dm_qn_subscriptions”中。

如果我稍后(应用程序关闭后)执行应该触发此订阅的条件,它似乎会触发,因为 SQL Server 在事件查看器中记录了一条信息消息,大意是:

对话句柄上的查询通知对话框 '{3F03B693-C0A5-E211-A97B-E06995EBDB20}.' 因以下原因关闭 错误:'<?xml version="1.0"?><Error xmlns="http://schemas.microsoft.com/SQL/ServiceBroker/Error"><Code>-8490</Code><Description>Cannot find the remote service &apos;SqlQueryNotificationService-0ea1f686-e554-4e25-aa7d-4f6d85171cc3&apos; because it does not exist.</Description></Error>'

然后订阅会从“sys.dm_qn_subscriptions”中删除。

注意:当应用程序处于活动状态时,订阅也会正确触发。就我的应用程序而言,没有任何问题,但让我担心的是,一旦它们所依赖的代理队列/服务终止,订阅就不会在数据库系统表中自动擦除。这可能(至少)导致数据库中积累大量幻影/不死订阅记录,并在事件查看器中产生不必要的 SQL Server 清理消息(每次应用运行都会在“sys.dm_qn_subscriptions”中生成新的不死订阅记录)。

这种行为正常吗?可以把东西弄得更整洁吗?

【问题讨论】:

    标签: .net sql-server service-broker sqldependency event-viewer


    【解决方案1】:

    这是正常行为。 QN 寿命很长,它们会在数据库重新启动时触发(因此也会在服务器重新启动后触发)。但是SqlDependency 设置了一个临时服务/队列来接收通知,并且应该在崩溃的情况下使用dialog timerinternal activation 来拆除这些通知。这两种机制交互的方式就是你所看到的,即 ERRORLOG 污染。没有不好的发生,at least not usually,但显然不整洁。

    可以让事情变得更整洁吗?

    您可以直接使用SqlNotificationRequest 推出您自己的解决方案,它不再提供创建服务/队列以接收您的应用程序域通知并将它们路由到适当的SqlDependency.OnChange 事件的“服务”。有可行的替代方案,具体取决于具体情况。但这是相当低级的工作,您最终可能会以比原始SqlDependency 解决方案更糟糕的方式解决问题......

    顺便说一句,无法在应用程序退出时“删除”挂起的 QN 订阅。该问题是 QN 用作通知传递机制的单向对话所固有的。适当的通知(订阅)应该由订阅者发起,并且通知应该是从目标(通知者)返回给发起者(订阅者)的响应消息。

    【讨论】:

    • 非常感谢您的回复,莱姆斯!实际上,我想知道如何才能让您特别关注此事,因为您的名字总是与 Internet 上的 SqlDependency 建议相关 :) 如果可以的话,还有一个问题:如果这是预期的行为,并且假设我这样做不希望重新实现 SqlDependency,那么我最好的策略是让 SQL Server 清理服务不那么健谈吗?有什么现成的线索吗?如果没有,别担心:我会自己做一些额外的研究。再次感谢和欢呼!
    • just make the SQL Server clean-up service less chatty 恐怕这不可能……
    • 这是正常行为似乎很奇怪。 sys.dm_qn_subscriptions 什么时候被清除?
    【解决方案2】:

    如果你不介意有点俗气,我已经找到了一种在退出时清理它们的方法......

    首先,设置一个 onDependencyChange 可以观察到的标志,让它知道重新订阅查询。

    其次,设置标志并执行你知道会触发依赖订阅的无操作更新。

     update foo_master set foo_bar = foo_bar where foo_id = @id;
    

    我的依赖监控是在单独的行上完成的,所以我只需要搔痒一排就可以让它触发。这可能不是您想要在大型结果集上执行的操作。

    在我的 FormClosing 事件中,我在断开连接之前触发每个依赖项。

    部分代码:

    Private _dependency As SqlDependency = Nothing
    Private _beingKilled = False
    
    ' dependency is set up in loadRecord(ByVal idRow as Integer)
    
    Private Sub onDependencyChange(ByVal sender As Object, ByVal e As SqlNotificationEventArgs)
        ' This event may occur on a thread pool thread; It is illegal to update the UI from a worker thread.
        ' The following code checks to see if it is safe update the UI.
        Dim iSync As ISynchronizeInvoke = CType(_connection.masterForm, ISynchronizeInvoke)
    
        ' If InvokeRequired returns True, the code is executing on a worker thread.
        If iSync.InvokeRequired Then
            Dim tempDelegate As New OnChangeEventHandler(AddressOf onDependencyChange) ' Create a delegate to perform the thread switch
            Dim args() As Object = {sender, e}
            iSync.BeginInvoke(tempDelegate, args) ' Marshal the data from the worker thread to the UI thread.
        Else
            RemoveHandler _dependency.OnChange, AddressOf onDependencyChange
            If Not _beingKilled Then loadRecord(_id)
        End If
    End Sub
    

    然后只需将 _beingKilled 设置为 True 并执行无操作更新。

    【讨论】:

    • 或者另一种方法是删除处理程序,然后对订阅进行搔痒...例如,如果您要订阅新行,请删除处理程序,然后搔痒旧的行,然后订阅新查询。退出时,删除处理程序,对旧行进行搔痒并订阅新内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-22
    • 2022-07-22
    • 1970-01-01
    相关资源
    最近更新 更多