如何清除SQL Server内存中的SqlDependency?

14

如何清理SQL Server中已过期的SqlDependency对象?在接收到SqlDepedency对象的事件后,我需要创建一个新的对象才能获取新的事件。但是,SQL Server进程的内存使用会不断上升,直到用尽允许的内存(SQL Server Express)。如何摆脱旧查询?

代码:

// Func: RegisterTableListener
using (SqlConnection cn = new SqlConnection(Properties.Settings.Default.DatabseEventConnectionString))
{
if (cmd == null)
{
    cmd = cn.CreateCommand();

    cmd.CommandType = CommandType.Text;
    cmd.CommandText = "SELECT HostName, LastStatus, LastDetails, xml FROM dbo.[SystemTable]";
}

lock (cmd)
{
    cmd.Connection = cn;
    cn.Open();
    cmd.Notification = null;

    //  creates a new dependency for the SqlCommand
    if (dep == null)
        dep = new SqlDependency(cmd);
    //  creates an event handler for the notification of data
    //      changes in the database.
    dep.OnChange += new OnChangeEventHandler(dependency_OnChange);


    using (SqlDataReader reader = cmd.ExecuteReader())
    {
    // code here to read
    }
}
}

// Func dependency_OnChange
//SqlDependency dep = sender as SqlDependency;
dep.OnChange -= dependency_OnChange;
RegisterTableListener();

你是如何创建 SqlDependency 对象的?请把你的代码贴出来。你是否正确地处理了它们的释放? - Oded
明天上班时我会更新我的评论并附上代码。Sudo: SqlDependency dep = new SqlDependency(cmd); dep.OnChange += fun;SqlDependency没有实现IDisposable。 - JeremyK
我已经更新了代码。即使我只运行一个 SqlDepdency 实例并每次调用 Stop 和 Start,内存也会不断上升。我对发生的事情毫无头绪。 - JeremyK
1个回答

21

Microsoft SqlDependency类具有特定的行为。即使您调用了SqlDependency.Stop()方法并释放了SqlCommand和SqlConnection,它仍然会在数据库中保留对话组(sys.conversation_groups)和对话端点(sys.conversation_endpoints)。看起来SQL Server加载了每个对话端点并使用了所有允许的内存。这里是证明这一点的测试。因此,要清除所有未使用的对话端点并释放所有占用的内存,您需要针对数据库启动此SQL代码:

DECLARE @ConvHandle uniqueidentifier
DECLARE Conv CURSOR FOR
SELECT CEP.conversation_handle FROM sys.conversation_endpoints CEP
WHERE CEP.state = 'DI' or CEP.state = 'CD'
OPEN Conv;
FETCH NEXT FROM Conv INTO @ConvHandle;
WHILE (@@FETCH_STATUS = 0) BEGIN
    END CONVERSATION @ConvHandle WITH CLEANUP;
    FETCH NEXT FROM Conv INTO @ConvHandle;
END
CLOSE Conv;
DEALLOCATE Conv;

另外,SqlDependency无法让您有机会接收表格的所有更改。因此,在SqlDependency重新订阅期间,您不会收到有关更改的通知。

为了避免所有这些问题,我使用了另一个开源实现SqlDependency类的工具——SqlDependencyEx。它使用数据库触发器和本地服务代理通知来接收有关表格更改的事件。以下是使用示例:

int changesReceived = 0;
using (SqlDependencyEx sqlDependency = new SqlDependencyEx(
          TEST_CONNECTION_STRING, TEST_DATABASE_NAME, TEST_TABLE_NAME)) 
{
    sqlDependency.TableChanged += (o, e) => changesReceived++;
    sqlDependency.Start();

    // Make table changes.
    MakeTableInsertDeleteChanges(changesCount);

    // Wait a little bit to receive all changes.
    Thread.Sleep(1000);
}

Assert.AreEqual(changesCount, changesReceived);

希望这可以帮到您。


1
这行代码的含义是什么:WHERE CEP.state = 'DI' or CEP.state = 'CD'。你想做什么?请帮我理解一下,谢谢。 - Mou
@Mou DI 表示 "DisconnectedInbound",CD 表示 "Closed"。带有这些标记的会话端点不会被 SqlDependency 设置生命周期。这意味着它们将一直存在于数据库中,直到您强制清除它们。根据文章 http://rusanu.com/2014/03/31/how-to-prevent-conversation-endpoint-leaks/(最后)的说法,这是清理旧会话端点的正确方法。 - dyatchenko
这是否意味着如果我使用这个 SQL WHERE CEP.state = 'DI' or CEP.state = 'CD',那么旧的对话将被删除? - Mou
@Mou 这是关于内存泄漏的另一个证据,并且发明者对此进行了评论。 最终,您可以找到类似的解决方案。 https://groups.google.com/forum/#!topic/microsoft.public.sqlserver.server/KtJZ0cdVk7k 这里有单元测试显示 SqlDependency 在数据库中保留未关闭或寿命缺失的会话端点。 https://github.com/dyatchenko/ServiceBrokerListener/blob/master/ServiceBrokerListener/ServiceBrokerListener.UnitTests/SqlDependencyTest.cs - dyatchenko
@Mou 我上面提到的 Rusanu 的文章给出了一个仅清理旧对话端点状态为 CD 的示例。但是根据我的经验,状态为 DI 的端点也会一直存在于数据库中。 - dyatchenko
使用SqlDependency的好模式是什么? - Kiquenet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接