我有一个在 Linux Kubernetes 容器中使用 .NET 6 运行的分析应用程序。它接收 http 消息并记录它们。
对于我的数据层,我使用的是Microsoft.Data.SqlClient
v4.1.0 库和直接的 ADO.NET 连接 - 如下所示:
var connString = ConnectionService.GetConnectionString();
using var conn = new SqlConnection(connString);
using var cmd = new SqlCommand(...sql..., conn);
cmd.Parameters.Add("@....", SqlDbType...).Value = ...;
try
{
await cmd.Connection.OpenAsync();
await cmd.ExecuteNonQueryAsync();
}
catch (Exception ex)
{
Log.Error(ex);
await cmd.Connection.CloseAsync();
throw;
}
await cmd.Connection.Close();
分析服务平均每秒大约 250 次写入。它目前正在写入 SQL Server 2017(有升级计划,但尚未发生)。
观察 SQL Server Profiler,写入需要 1 毫秒到 6 毫秒。从来没有长期运行的东西。使用此查询检查睡眠会话:
SELECT login_name,
COUNT(*) [sleeping_sessions]
FROM sys.dm_exec_sessions
WHERE database_id > 0
AND status = 'sleeping'
GROUP BY login_name
ORDER BY 2 DESC
在撰写本文时,有 23,173 个休眠连接。
我在开发服务器上运行了一个封闭测试,我可以看到当应用程序打开一个连接并关闭它时,该连接处于睡眠状态。它仅在应用程序停止时消失。
我的理解是,这就是 ADO.NET“连接池”的工作方式。但是,我的印象是有 100 个连接限制,除非另有增加。
我将 SQL 进程更改为同步运行:
try
{
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
Log.Error(ex);
cmd.Connection.Close();
throw;
}
cmd.Connection.Close();
但这并没有改变什么。仍然有 20k+ 休眠连接。
我在连接和命令对象上都利用了名为using 语句的 C# 8.0 功能。它在语义上等同于将代码块包装在using
with 中{}
。
我正在使用单个连接字符串,每个连接字符串都相同,并且安全上下文相同。连接字符串是从app.config
设置中提取的。
我在连接字符串中尝试了老式using
块 + 同步 + 最大池大小 = 100,但没有影响。仍然是 20k+ 睡眠时间。它从 0 开始并不断上升,但最终趋于平稳。
有人对我应该采取哪些步骤有任何建议吗?SQL Server 最多只允许 32,767 个连接,所以我正在接近危险区域。
对于遇到此问题的其他任何人,我向 MS 开了一张票并能够得到解决方案。
我在原始问题中的缩写代码不包括我的 SqlCredential 逻辑,这是问题的根源。
以下是从服务生成 SqlConnection 的完整代码:
问题是,通过每次创建一个新的 SqlCredential 对象,我正在分割连接池。尽管连接字符串本身和用户名/密码相同,但凭据对象的新实例足以让 ADO.NET 继续创建新的连接池。
我开始缓存和重用同一个 SqlCredential 对象,休眠连接的数量从 20k+ 下降到大约 6 个。