ADO.net 关闭 TCP 连接的速度不够快
ADO.net is not closing TCP connections fast enough
我是 运行 一个多线程操作,它使用 SqlConnection
和 Parallel.ForEach()
从 SQL 服务器数据库中获取数据,并且发生以下情况:
- 我将
SqlConnection
包装在 using
语句中,以便正确处理连接。
- 我的进程在 运行 成功一段时间后一直抛出包裹在
AggregateException
中的 SqlException
("A network-related or instance-specific error occurred while establishing a connection to SQL Server..")
- 我发现这发生在对数据库的 2^14 (16384) 次调用(总共,跨所有线程)。
- 我启动了 perfmon,我可以看到这也是在抛出异常时打开的 TCP 连接数("Connections Established" 计数器)。
- 我确定我的代码中没有连接泄漏 - 我查询数据库的地方很少,它们都正确处理了连接(事实上,我没有使用其他模式来查询香草以外的数据库
using(...)
)
- 我关闭了连接池并且发生了相同的行为。
- 奇怪的是,如果我删除 SQL 服务器中的一个索引使我的查询速度变快,操作会成功完成(尽管非常慢)——不会抛出异常。我观察到 # of Connections Established 线性上升到大约 13K,然后稳定了一段时间,然后有一些线性下降的时期,同时操作是 运行.
- 我的结论是,建立索引后,.net 处理数据的速度快于它关闭连接的速度,并最终达到某种 OS 或 .NET 套接字最大阈值。没有索引,.NET 仍然维护着太多的连接,但它有时间关闭足够多的连接,以免达到最大打开套接字阈值。
我不知道如何指示 .NET 关闭这些连接。我认为当 SqlConnection
被处理时,这会自动神奇地发生。
即使连接对象被释放回池中,非托管资源也可能在执行垃圾回收之前不会被释放。通过减慢命令速度(删除该索引),您为 GC 提供了足够的时间将资源释放回 OS.
如果您不使用异步命令执行,您可以尝试在线程级别缓存连接对象。然后,并行操作将继续重复使用相同的打开连接,而不是获取新套接字等。
我没有对异步操作进行任何测试以防止某些资源过度使用。我相信他们会使用 IO 完成端口来防止阻塞。而且,您肯定不希望在该状态下使用同一个连接对象的两个命令对象。
问题似乎与连接的打开方式有关。我正在使用这个构造函数
public SqlConnection(string connectionString, SqlCredential credential)
似乎 ADO 创建了一个新的连接池,即使 connectionString
相同并且 credential
封装了相同的凭据。我的猜测是 ADO 无法以某种方式将凭据与先前的调用相关联(如果我通过引用相等性使用相同的凭据对象,它可能会起作用?在我的例子中,我在每次调用时创建一个新的 SqlCredential)。
由于每次都会创建一个新的池,TCP 连接数会激增。我认为这在关闭连接池时也有意义。可能一些套接字或 TCP 设置在这里启动(保持活动状态?)并且 OS 保持连接打开以遵守这些设置。
我是 运行 一个多线程操作,它使用 SqlConnection
和 Parallel.ForEach()
从 SQL 服务器数据库中获取数据,并且发生以下情况:
- 我将
SqlConnection
包装在using
语句中,以便正确处理连接。 - 我的进程在 运行 成功一段时间后一直抛出包裹在
AggregateException
中的SqlException
("A network-related or instance-specific error occurred while establishing a connection to SQL Server..") - 我发现这发生在对数据库的 2^14 (16384) 次调用(总共,跨所有线程)。
- 我启动了 perfmon,我可以看到这也是在抛出异常时打开的 TCP 连接数("Connections Established" 计数器)。
- 我确定我的代码中没有连接泄漏 - 我查询数据库的地方很少,它们都正确处理了连接(事实上,我没有使用其他模式来查询香草以外的数据库
using(...)
) - 我关闭了连接池并且发生了相同的行为。
- 奇怪的是,如果我删除 SQL 服务器中的一个索引使我的查询速度变快,操作会成功完成(尽管非常慢)——不会抛出异常。我观察到 # of Connections Established 线性上升到大约 13K,然后稳定了一段时间,然后有一些线性下降的时期,同时操作是 运行.
- 我的结论是,建立索引后,.net 处理数据的速度快于它关闭连接的速度,并最终达到某种 OS 或 .NET 套接字最大阈值。没有索引,.NET 仍然维护着太多的连接,但它有时间关闭足够多的连接,以免达到最大打开套接字阈值。
我不知道如何指示 .NET 关闭这些连接。我认为当 SqlConnection
被处理时,这会自动神奇地发生。
即使连接对象被释放回池中,非托管资源也可能在执行垃圾回收之前不会被释放。通过减慢命令速度(删除该索引),您为 GC 提供了足够的时间将资源释放回 OS.
如果您不使用异步命令执行,您可以尝试在线程级别缓存连接对象。然后,并行操作将继续重复使用相同的打开连接,而不是获取新套接字等。
我没有对异步操作进行任何测试以防止某些资源过度使用。我相信他们会使用 IO 完成端口来防止阻塞。而且,您肯定不希望在该状态下使用同一个连接对象的两个命令对象。
问题似乎与连接的打开方式有关。我正在使用这个构造函数
public SqlConnection(string connectionString, SqlCredential credential)
似乎 ADO 创建了一个新的连接池,即使 connectionString
相同并且 credential
封装了相同的凭据。我的猜测是 ADO 无法以某种方式将凭据与先前的调用相关联(如果我通过引用相等性使用相同的凭据对象,它可能会起作用?在我的例子中,我在每次调用时创建一个新的 SqlCredential)。
由于每次都会创建一个新的池,TCP 连接数会激增。我认为这在关闭连接池时也有意义。可能一些套接字或 TCP 设置在这里启动(保持活动状态?)并且 OS 保持连接打开以遵守这些设置。