.NET SqlConnection类,连接池和重连逻辑

8
我们有一些客户端代码,使用.NET中的SqlConnection类与SQLServer数据库通信。它偶尔会出现以下错误:"ExecuteReader需要一个打开且可用的连接。连接的当前状态为Closed"。 "临时"解决方案是重新启动进程,之后一切正常 - 然而,这显然是不令人满意的。该代码保留了SqlConnection实例的缓存,每个数据库一个。
我们想要重写代码,但在此之前,我需要知道一些事情:我的第一个问题是:重复连接和断开SqlConnection对象是否低效,还是底层库代表我们执行连接池?
// Is this bad/inefficient?
for(many-times)
{
    using(SQLConnection conn = new SQLConnection(connectionString))
    {
        // do stuff with conn
    }
}

因为我们的代码没有执行上述操作,因此问题的可能原因是在连接的“寿命”期间发生了某些导致连接关闭的底层SQLServer数据库的情况...
如果证明“缓存”SqlConnection对象是值得的,那么处理所有可以通过“重新连接”到数据库简单解决的错误的推荐方式是什么?我所说的情况包括:
- 数据库被脱机并重新联机,但客户端过程在此过程中没有打开任何事务 - 数据库已“断开”,然后“重新连接”
我注意到SqlConnection上有一个“State”属性...有适当的方法来查询它吗?
最后,我设置了一个具有完全访问权限的测试SQLServer实例:如何复制粘贴“ExecuteReader需要打开和可用的连接。连接的当前状态为Closed”的确切错误?
3个回答

22
不,创建许多SqlConnection对象并在完成后关闭每个对象并不低效。这正是正确的做法,让.NET框架连接池来完成它的工作-不要试图自己实现。您无需执行任何特定操作以启用连接池(尽管您可以通过在连接字符串中设置Pooling=false来禁用它)。
如果您尝试自行缓存连接,可能会出现许多问题。拒绝这种做法吧 :)

我希望听到的是...你有什么想法可以让我重现我们所看到的确切错误吗?(只是为了“证明”我已经解决了这个问题)? - Paul Hollingsworth
说实话很难确定,如果你试图从多个线程使用同一连接,那么很可能是竞态条件。 - Jon Skeet
1
禁用连接池:在连接字符串中添加“Pooling=False;”。 - Richard
@Richard:是的,我在你的评论之前几分钟就添加了那个。 - Jon Skeet
@JonSkeet 很抱歉打扰一个如此古老的话题。但是 GC 成本呢?我是说,即使底层的 SQL 连接是池化的(当释放连接时,该连接可能仍然被重置,这也会导致对 DB 的调用:sp_reset_connection),创建的 SqlConnection 实例也需要被 GC。那么为什么不将循环放在 using 块内并保存连接重置和 GC 成本呢?我能想到的唯一原因就是每个 DB 操作都有可能具有特定的事务安全性。所以,你在 2009 年回答这个问题时还考虑了其他什么?;-) - cmart
原来内置连接池存在一个关键级别的错误。我在这里寻找注意事项,试图构建自己的连接池时,发现内置连接池已知对我们不可用。 - Joshua

2
您应该在连接字符串上启用连接池。在这种情况下,运行时会在您关闭它们时将连接添加回“池”中,而不是真正意义上的断开连接。当从池中取出“新”的连接时,它将被重置(即调用sp_reset_connection),然后作为全新、新鲜的连接呈现给应用程序。池会自动处理这些情况,例如当连接在池中处于闲置状态时关闭的情况。
从头开始创建新连接的成本很高,因为身份验证需要客户端和服务器之间进行几次往返(根据身份验证方法和SSL设置,最好情况下可能只需要1次往返,而最坏情况下则需要约10次)。
至于你的问题,连接在其状态发生变化时会引发OnStateChange事件,但如果你使用连接池,则不需要关心这个。

连接池是默认设置,您无需进行任何操作即可启用它。您可以禁用它,但这是一个罕见的要求。 - Richard
1
请注意,如果您启用了连接池并打开连接以检查数据库是否存在,然后根据此信息创建数据库。那么sp_reset_connection技巧将无法使您看到数据库。因此,请在不使用连接池的情况下进行第一次连接。 - Anders Rune Jensen
我遇到了一个问题,我认为这是由于连接池引起的。假设网络连接断开了几毫秒,ping 是成功的,telnet 到 SQL 服务器端口也连接了,但 .NET 应用程序仍然会给出连接错误,直到我从 cpanel 禁用/启用网络或重新启动应用程序。即使连接是在 using(){} 块中创建的,似乎 .net 也不会尝试重新连接。似乎这是在 .NET 4.7 及以上版本上发生的,因为相同的代码在 4.5 和 4.0 上运行良好。 - AaA

1
在我的最近经验中,如果你使用这段代码:

using(SQLConnection conn = new SQLConnection(connectionString))
{
    // do stuff with conn
}

如果出现错误,并且没有显式关闭连接,则该连接将不会被关闭或返回到池中。因此,请使用catch或finally块来关闭连接。


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