使用 Microsoft.Data.SqlClient 的 SQL Server 连接池

Microsoft.Data.SqlClient 连接池会重用经过身份验证的物理连接。 SqlConnection.Open 或者 OpenAsync 检查池中是否有可用的连接。 Close、Dispose 或 DisposeAsync 会将其重置并返回。 这种方法避免了每次操作都需要网络连接、认证和会话设置。

默认情况下,池化是启用的。 请使用以下应用模式:

await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync(cancellationToken);

using var command = new SqlCommand(sql, connection);
await command.ExecuteNonQueryAsync(cancellationToken);

延迟打开,尽早释放,让池来管理物理连接。 不要在全局范围内保留一个SqlConnection处于打开状态。

选择连接池实现

SqlClient 包含两个连接池实现。 V1 池是默认的。 从 Microsoft.Data.SqlClient 7.1 开始,你可以通过在应用启动时将 Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2 AppContext 切换设置为 true 来选择加入基于通道的 V2 池:

AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2", true);

连接字符串池化控制适用于两种实现。 有关该开关及其默认设置的更多信息,请参见 启用 V2 连接池。

了解池密钥

连接只能从其对应的连接池中复用。 池密钥包含的不仅仅是目标服务器。

Input 池塘行为
连接字符串 文字必须完全一致。 关键词顺序的差异会产生独立的池,即使有效设置相同。
Windows 集成身份验证 Windows 身份是密钥的一部分。 同一字符串在不同身份下使用会创建不同的池。
SqlCredential 对象实例是密钥的一部分。 即使包含相同的用户名和密码,独立实例也会创建不同的池。
SqlConnection.AccessToken 访问令牌的值是密钥的一部分。 替换令牌字符串可能会创建新的连接池,并使现有连接池中保留那些仍使用旧令牌完成身份验证的连接。
SqlConnection.AccessTokenCallback 回调是键的一部分。 对于应该共享池的连接,重复使用同一个回调实例。 返回的代币值不是池密钥。
自定义SSPI上下文提供者 提供者实例参与连接配置。 对于应共享同一个连接池的连接,请复用同一个提供程序实例。
环境事务 已登记的连接在匹配池内使用针对事务的细分。

数据库、身份验证模式、加密选项、应用程序名称、池化选项以及连接字符串中的所有其他值,都由同一个连接字符串决定。

建立一个规范的连接字符串并重复使用。 避免在 Application Name、Workstation ID 或其他关键字中使用针对每个请求的值。

选择可以池化的令牌 API

对于 Microsoft Entra ID 访问令牌,请使用 Microsoft.Data.SqlClient 提供的身份验证模式或稳定的 AccessTokenCallback。

AccessTokenCallback是在 Microsoft.Data.SqlClient 5.2 中引入的。 驱动程序在需要令牌时会调用它,并且可以为复用的池请求刷新后的令牌。 保持驱动提供的认证参数的回调确定性,并重复使用同一个代理实例。

当代码直接设置 AccessToken 时:

  • 令牌字符串成为池密钥的一部分。
  • 应用程序负责令牌的过期和刷新。
  • 连接池中的物理连接,其存续时间可能长于用于创建该连接的令牌。
  • 如果该池已无法安全使用,请在替换过期令牌后调用 ClearPool。

不要为每个请求都创建新的回调 Lambda 表达式或凭据对象。 对象身份差异可能导致池的碎片化。

Microsoft。Data.SqlClient 7.0 增加了SspiContextProvider自定义 Kerberos 或 NTLM 协商功能。 将提供者视为应用范围的连接配置,而非每个请求的状态。

每个池的规模

这些连接字符串选项控制一个池。 完整关键词参考请参见 ConnectionString。

关键 字 默认 Effect
Pooling true 启用或禁用池化。
Min Pool Size 0 设定池创建后保留的最少物理连接数。
Max Pool Size 100 设定池中物理连接的最大数量。
Connect Timeout 15 秒 设置在没有可用连接时 Open 的等待时长。
Load Balance Timeout 0 秒 如果连接的年龄超过配置值,返回池时会丢弃该连接。 Connection Lifetime 是别名。
Connection Idle Timeout 300 从 7.1.0 版本开始,闲置连接在超过该秒数后会被驱逐。 值为0则禁用闲置过期。 强制实施时,要求 UseLegacyIdleTimeoutBehavior AppContext 开关为 false。

随着需求增长,池子会创建连接,直到达到 Max Pool Size。 当所有连接都已使用时,后续打开等待连接返回。 如果等待时间超过 Connect Timeout,则开局失败。

检查前不要提交 Max Pool Size:

  • 每条路径上都布置了各个连接和读取器。
  • 命令和交易都能迅速完成。
  • 查询工作负载没有被阻塞或过于饱和。
  • 数据库连接上限可以处理每个应用程序实例中每个池数量乘以 Max Pool Size。

Min Pool Size 为正值时,会在空闲期间保持连接处于打开状态。 只有在测量结果证明需要热连接时才使用。 它通常不利于缩容至零、无服务器自动暂停以及突发型云架构设计。

在默认的 Load Balance Timeout=0 情况下,定期清理通常会在大约四到八分钟后移除高于 Min Pool Size 的未使用连接,或者在检测到服务器连接断开时由池将其移除。 应将该时间间隔视为实现层面的行为特征,而不是针对每个连接的空闲时长保证。 连接池不会在每次检出连接前都发送验证查询,因为那样的往返开销会抵消连接池带来的大部分收益。

连接空闲时间限制

从 Microsoft.Data.SqlClient 7.1.0 开始,使用 Connection Idle Timeout 配置空闲过期检查和池化连接的后台清理。 默认值为 300 秒。 值为0则禁用闲置过期。 负值则抛出 ArgumentException。

Connection Idle Timeout=120

你也可以在构建 连接字符串 时设置 SqlConnectionStringBuilder.IdleTimeout:

var builder = new SqlConnectionStringBuilder(connectionString)
{
    IdleTimeout = 120
};

空闲超时的强制执行需手动启用。 在应用程序启动时将以下 AppContext 开关设置为 false:

AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseLegacyIdleTimeoutBehavior", false);

开关和连接字符串设置对每个池实现的影响不同:

  • V1泳池。 当开关为 true 时,V1 使用历史清理频率(2 到 4 分钟之间的随机时长),并且无论 Connection Idle Timeout 的值为何,都会逐出空闲连接。 当开关为 false 且 Connection Idle Timeout 为非零时,V1 使用配置超时的一半作为清理频率,并在一到两个清理周期后驱逐空闲连接。 当开关为 false 和 Connection Idle Timeout=0 时,V1 会禁用空闲连接逐出,但会以历史间隔(2 到 4 分钟之间的随机时长)继续维持最小池大小。
  • V2 池。 当开关为 true时,V2 不会对每个连接进行空闲年龄检查,但非零 Connection Idle Timeout 会启用并配置后台剪枝。 当开关为 false 且 Connection Idle Timeout 非零时,V2 会对每个连接进行空闲时长检查,并根据超时值配置后台剪枝。 当开关为 false 和 Connection Idle Timeout=0 时,V2 不会对每个连接执行空闲时长检查或后台删除。 当 Min Pool Size 大于或等于 Max Pool Size 时,V2 不会执行背景剪枝。

如果池化器检测到与服务器的连接被切断,两个池实现也会移除连接。

处理认证封锁期

在认证超时或其他认证失败后,池可能会进入阻塞期。 在此期间,匹配的开放尝试会重新抛出原始异常,而无需再次尝试认证。

首次阻止期为五秒。 又一次失败后,时间会翻倍到一分钟。

Pool Blocking Period 控制此行为:

价值 Behavior
Auto 对普通 SQL Server 端点启用阻止,并对已识别的 Azure SQL 端点后缀禁用阻止。 虚 DNS 名称可能不会接收 Azure 行为。
AlwaysBlock 为每个终结点启用阻止时间段。
NeverBlock 禁用阻挡期。

保留 Auto,除非应用程序经验证的重试设计要求作出不同的选择。 禁用封锁期可能会将凭证、防火墙或故障问题变成认证风暴。

阻断时间和可配置的重试逻辑是分开的。 在阻塞期间打开同一池的重试提供者会收到缓存异常。

管理连接生存期和清除

当池检测到致命错误(例如故障切换)时,会自动清空受影响的池。 连接池会关闭空闲连接,并在已检出连接归还时将其丢弃。

使用清除 API 处理已知的配置或凭据边界:

  • ClearPool 清除与某个 SqlConnection 配置关联的池。
  • ClearAllPools清除进程或应用程序域中的所有 Microsoft.Data.SqlClient 连接池。

池关闭了清空池中的空闲连接。 池会标记当前正在使用的连接,因此在返回时会丢弃它们。

清空池会导致后续打开操作执行实际登录。 请勿将其用作定期维护、通用错误处理程序,或替代处理连接。

Load Balance Timeout 提供基于年龄的渐进人员流动。 当部署或集群服务需要让原有的物理连接随着时间逐步断开时,请使用它。 确认所选值不会导致过多的硬连接。

了解事务

使用默认设置 Enlist=true 时,在 System.Transactions.Transaction.Current 内部打开的连接会自动加入该事务。

当一个已加入事务的连接关闭时,连接池会将其放入事务专用的子池中。 同一交易下后续的开启可以重复使用。 物理连接直到交易完成后才会返回到通用池。

因此,长期或废弃的环境事务会:

  • 把物理连接排除在一般资源池之外。
  • 在逻辑连接关闭后仍然消耗池容量。
  • 保持服务器锁和事务状态有效。

保持交易有界限,明确完成,并监控停滞连接。 仅当连接必须保持在环境事务之外时,才设置 Enlist=false。

防止池碎片化

池碎片化会产生许多小池,而不是几个可重复使用的池。 常见原因包括:

  • 连接字符串关键字顺序或别名差异。
  • 每个客户、用户、请求或数据库对应一个连接字符串。
  • 在多个 Windows 身份下集成认证。
  • 新 SqlCredential、访问令牌回调或按请求创建的 SSPI 提供程序实例。
  • 每次刷新时都会更换直接访问令牌。
  • 高基数应用程序名称或工作站 ID。

使用 SqlConnectionStringBuilder 规范化连接字符串,并集中处理连接创建。

如果应用程序有意连接多个数据库或身份,应将所得的池计数纳入容量规划。 不要使用不受信任的数据库名称来运行 USE 来合并池。 数据库隔离、权限、会话状态和池重置行为必须保持明确。

考虑应用程序角色和会话状态

池会在将物理连接分配给另一个逻辑连接之前,重置可重用的 SQL Server 会话状态。 应用代码仍应在其工作单元内设置所需的会话状态。

使用 sp_setapprole 激活的 SQL Server 应用程序角色无法安全地重置回常规池化状态。 优先使用数据库用户、包含的用户、角色、行级安全或其他授权设计。 如果某个应用角色不可避免,请采用有文档记录的基于 Cookie 的反转模式,或者在测试后对该隔离路径禁用池。

释放读取器,完成或回滚事务,并且不要在连接关闭时让命令继续运行。 不要指望临时表或其他会话状态能够跨逻辑连接保留下来。

使用云托管的池化模式

对于 Azure 应用服务、Azure Functions、容器、Kubernetes 和其他横向扩展的主机:

  • 计算所有实例、进程、池键和副本之间可能的数据库连接。
  • 使用管理身份或稳定访问令牌回调,而不是在连接对象中轮换令牌字符串。
  • 保持 Min Pool Size=0,除非经过测量的冷启动要求证明有必要使用保留会话。
  • 预计新实例会从一个空池开始。
  • 在服务相同工作负载的实例之间保持连接字符串一致。
  • 限制连接尝试次数和重试次数,以避免在故障切换或横向扩展期间出现同步登录激增。
  • 设置 MultiSubnetFailover=true,用于 Azure SQL 和其他受支持的多地址 TCP 端点。

连接池是应用进程本地的。 它们不会在应用实例、容器或主机之间共享。

诊断池行为

使用 SqlClient 诊断计数器 观察以下内容:

  • 硬连接和断开连接,代表物理服务器连接。
  • 软连接和断开连接,分别代表池的检出与归还。
  • 活动连接和空闲池化连接。
  • 主动池组和池。
  • 停滞连接。
  • 重新收回连接,应用代码没有丢弃逻辑连接。

将客户端计数器与 SQL Server 会话、等待、阻塞和资源限制相关联。 池超时可能意味着连接泄漏、查询缓慢、事务阻塞、过多并发、池碎片化或数据库容量限制。

使用 事件源跟踪 对池化器进行有针对性的跟踪。 跟踪信息过于冗长。 启用它作为有界诊断窗口,并保护捕获的连接元数据。

生产核对清单

  • 使池保持启用状态。
  • 对每个工作负载和数据库,重复使用同一个标准连接字符串。
  • 在每条路径上丢弃连接、命令、读取器和事务。
  • 复用凭据、令牌回调和SSPI提供者实例。
  • 为连接和命令设置有限的超时时间。
  • 为每个应用实例计算总连接预算。
  • 监控硬连接数、连接池计数、空闲连接数、停滞状态和超时。
  • 仅在提供程序无法检测到凭据、令牌或配置边界,或诊断确认仍存在陈旧连接时,才清除连接池。
  • 在生产环境上线前,对横向扩展、故障转移和凭据刷新行为进行负载测试。