如何解决ASP.NET和SQL Server之间的连接池问题?

Amr*_*rhy 192 .net sql-server asp.net sql-server-2005

最近几天我们在网站上看到这条错误消息:

"超时已到期.在从池中获取连接之前已经过了超时时间.这可能是因为所有池连接都在使用中并且达到了最大池大小."

我们的代码暂时没有改变任何内容.我修改了代码以检查未关闭的打开连接,但发现一切都很好.

  • 我怎么解决这个问题?

  • 我需要编辑此池吗?

  • 如何编辑此池的最大连接数?

  • 高流量网站的推荐值是多少?


更新:

我需要在IIS中编辑某些内容吗?

更新:

我发现活动连接的数量在15到31之间,我发现在SQL Server中配置的最大允许连接数超过3200个连接,31个太多,或者我应该在ASP.NET配置中编辑一些东西?

spl*_*tne 195

在大多数情况下,连接池问题与"连接泄漏"有关.您的应用程序可能无法正确且一致地关闭其数据库连接.当您打开连接时,它们将保持阻塞状态,直到.NET垃圾收集器通过调用其Finalize()方法为您关闭它们.

您想确保您确实关闭了连接.例如,下面的代码将导致一个连接泄漏,如果之间的代码.OpenClose抛出异常:

var connection = new SqlConnection(connectionString);
connection.Open();
// some code
connection.Close();                
Run Code Online (Sandbox Code Playgroud)

正确的方法是:

var connection = new SqlConnection(ConnectionString);
try
{
     connection.Open();
     someCall (connection);
}
finally
{
     connection.Close();                
}
Run Code Online (Sandbox Code Playgroud)

要么

using (SqlConnection connection = new SqlConnection(connectionString))
{
     connection.Open();
     someCall(connection);
}
Run Code Online (Sandbox Code Playgroud)

当您的函数从类方法返回连接时,请确保在本地缓存它并调用其Close方法.您将使用此代码泄漏连接,例如:

var command = new OleDbCommand(someUpdateQuery, getConnection());
result = command.ExecuteNonQuery();
connection().Close(); 
Run Code Online (Sandbox Code Playgroud)

第一次调用返回的连接getConnection()未关闭.此行不是关闭您的连接,而是创建一个新的并尝试关闭它.

如果您使用SqlDataReader或a OleDbDataReader,请关闭它们.即使关闭连接本身似乎也可以解决问题,但在使用它们时需要额外的努力来明确地关闭数据读取器对象.


MSDN/SQL杂志上的这篇文章" 为什么连接池溢出? "解释了很多细节并提出了一些调试策略:

  • sp_whosp_who2.这些系统存储过程从sysprocesses系统表返回信息,该信息显示所有工作进程的状态和信息.通常,您将看到每个连接一个服务器进程ID(SPID).如果使用连接字符串中的"应用程序名称"参数命名连接,则可以轻松找到工作连接.
  • 将SQL Server Profiler与SQLProfiler TSQL_Replay模板一起使用以跟踪打开的连接.如果您熟悉Profiler,则使用sp_who比使用轮询更容易.
  • 使用性能监视器来监视池和连接.我马上讨论这个方法.
  • 在代码中监视性能计数器.您可以使用例程提取计数器或使用新的.NET PerformanceCounter控件来监视连接池的运行状况和已建立连接的数量.

  • 一个小的修正:GC从不调用对象的Dispose方法,只调用它的终结器(如果有的话).然后终结器可以在必要时对Dispose执行"后备"调用,尽管我不确定SqlConnection是否这样做. (3认同)

ajb*_*ven 31

安装.NET Framework v4.6.1后,由于此更改,我们与远程数据库的连接立即开始超时.

要修复,只需TransparentNetworkIPResolution在连接字符串中添加参数并将其设置为false:

服务器= myServerName;数据库= MyDatabase的; Trusted_Connection = TRUE; TransparentNetworkIPResolution =假

  • 这篇 Microsoft 文章解释了为什么此设置可能有帮助 https://techcommunity.microsoft.com/t5/sql-server-support/connection-timeout-issue-with-net-framework-4-6-1/ba-p/318791 (2认同)

Mar*_*ell 13

除非您的使用量增加,否则似乎不太可能存在积压的工作.IMO,最可能的选择是某些东西正在使用连接而不是立即释放它们.你确定using在所有情况下都在使用吗?或者(通过什么机制)释放连接?


Ivo*_*Ivo 13

在关闭连接或datareader之前,您是否检查过未关闭的DataReader和response.redirects?在重定向之前不关闭它们时,连接会保持打开状态.

  • +1 - 或返回DataReaders的函数 - Connection永远不会在您创建它们的函数之外关闭... (3认同)

Ale*_*rto 9

我们也经常在我们的网站上遇到这个问题.在我们的案例中,罪魁祸首是我们的统计/指数已经过时.这导致先前快速运行的查询(最终)变慢和超时.

尝试更新统计信息和/或重建受查询影响的表上的索引,看看是否有帮助.

  • 我认为这可以解释为什么查询会超时,但我不认为这可以解释为什么在尝试获取连接时会遇到超时. (3认同)

MPa*_*lak 9

虽然这对聚会来说是相当晚的事情,但我最近遇到了这个错误,并且我在搜索这个问题时发现没有任何东西给我带来除了连接泄漏之外的任何见解——这不是我的问题。

如果这可能对其他人有帮助,我将解释问题、解决方案以及我一路上发现的一些有用的东西,因为我在其他地方找不到它。

就我而言,根本问题是由于没有注释作为字符串的关键列的模型属性。举个例子:

public class InventoryItem
{
    public string StateCode {get;set;}
    public string CompanyId {get;set;}
    public int Id {get;set;}
    // more properties removed for simplicity
}
Run Code Online (Sandbox Code Playgroud)

在我的 DbContext 中,我将键列配置为复合键。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<InventoryItem>().HasKey(x => new { x.StateCode, x.CompanyId, x.Id });    
}
Run Code Online (Sandbox Code Playgroud)

SQL 表定义

CREATE TABLE [Example].[InventoryItem] (
    [Id] INT NOT NULL,
    [StateCode] VARCHAR (2) NOT NULL,
    [CompanyId] VARCHAR (50) NOT NULL,
    CONSTRAINT [PK_InventoryItems] PRIMARY KEY CLUSTERED ([StateCode] ASC, [CompanyId] ASC, [Id] ASC)
)
Run Code Online (Sandbox Code Playgroud)

C#查询代码

public List<InventoryItem> GetAllCompanyInventory(string stateCode, string companyId)
{
    using (var context = ContextFactory.CreateContext())
    {
        return await context.InventoryItems.Where(x => x.StateCode == stateCode && x.CompanyId == companyId).ToListAsync().ConfigureAwait(false);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,发生的情况是 stateCode 和 companyId 被参数化,NVARCHAR(450)并且查询正在使用CONVERT_IMPLICIT,这实际上导致系统无法正确使用索引。我的 CPU 和工作时间达到 100%,不知怎么的,这转化为连接池问题。向这些属性添加注释后,我的 CPU 从未超过 5%,也再也没有看到连接池问题。以下是帮助我识别此问题的一些内容。

修复方法是注释这些属性

public class InventoryItem
{
    [System.ComponentModel.DataAnnotations.Schema.Column(TypeName = "varchar(2)")]
    public string StateCode {get;set;}
    [System.ComponentModel.DataAnnotations.Schema.Column(TypeName = "varchar(50)")]
    public string CompanyId {get;set;}
    public int Id {get;set;}
    // more properties removed for simplicity
}
Run Code Online (Sandbox Code Playgroud)

在我进行此更改后,服务器几乎处于休眠状态。运行 sp_who 显示连接数量要少得多,而且几乎所有连接都在休眠。

在此过程中,有人提到使用sp_who来查看活动的连接。当我开始在本地运行代码时,我可以看到连接数从 0 变为最大允许数,所有代码都列出了我的主机名和正在运行的状态。所以我认为我的代码有错误,但实际上我们创建上下文的每个地方都被 using 语句正确包装。我失去了理智。然后我转变了想法,也许这只是一种症状,而不是真正的问题。这引导我找到问题所在。

我发现这篇文章最终导致了这个 SQL 语句,它确实帮助我确定了发生了什么。

SELECT TOP 20
    (total_logical_reads/execution_count) AS avg_logical_reads,
    (total_logical_writes/execution_count) AS avg_logical_writes,
    (total_physical_reads/execution_count) AS avg_phys_reads,
    (total_worker_time/execution_count) AS avg_cpu_over_head,
total_logical_reads, total_logical_writes, total_physical_reads,
total_worker_time, execution_count, total_elapsed_time AS Duration,
plan_generation_num AS num_recompiles,
statement_start_offset AS stmt_start_offset,
    (SELECT SUBSTRING(text, statement_start_offset/2 + 1,
        (CASE WHEN statement_end_offset = -1
            THEN LEN(CONVERT(nvarchar(MAX),text)) * 2
                ELSE statement_end_offset
            END - statement_start_offset)/2)
     FROM sys.dm_exec_sql_text(sql_handle)) AS query_text,
      (SELECT query_plan FROM sys.dm_exec_query_plan(plan_handle)) AS query_plan
FROM sys.dm_exec_query_stats a
--JUST CHANGE THE ORDER BY TO GET THE OTHER RESOURCES
ORDER BY (total_logical_reads + total_logical_writes)/execution_count DESC
Run Code Online (Sandbox Code Playgroud)

从这里,我可以获得有问题的查询文本和查询计划。我注意到,某些输出的总工作时间超过 40 亿,而其他繁重查询的总工作时间约为 3 亿。这些超高的工作时间是预料之外的,因为它们在搜索谓词中恰好具有索引键。看看实际的查询计划,我可以看到一些事情:

  1. 要读取的估计行数极高(例如,每次执行的估计行数仅为 1 时,> 1000 万行)
  2. 用于CONVERT_IMPLICIT
  3. 建议创建一个不同的索引,但实际上是我已经存在的索引(用简化的示例解释有点复杂,但实际索引有更多列,并且省略了前两列以避免转换)

这篇文章帮助我理解为什么即使有一个索引使用我正在搜索的确切键,我的估计行数也如此高。

本文帮助我理解了我正在查看的查询计划中的 CONVERT_IMPLICIT 调用。

我找到上面的文章是因为这篇文章中提到了它。

帮助我弄清楚如何将默认值从 nvarchar 更改为 varchar。

我仍然不确定为什么这会导致连接池超时问题。我运行了一些测试,这些测试一次会执行一个状态代码+公司 ID,但它仍然几乎立即遇到此错误。一旦我将其更改为具有注释,即使同时处理许多请求,一切也都清楚了。


Man*_*pel 7

我刚刚遇到了同样的问题,想分享一下帮助我找到源代码的方法:将应用程序名称添加到连接字符串中,然后监视与 SQL Server 的打开连接

select st.text,
    es.*, 
    ec.*
from sys.dm_exec_sessions as es
    inner join sys.dm_exec_connections as ec on es.session_id = ec.session_id
    cross apply sys.dm_exec_sql_text(ec.most_recent_sql_handle) st
where es.program_name = '<your app name here>'
Run Code Online (Sandbox Code Playgroud)


Uwe*_*eim 7

在我的情况下发生了另一个原因,因为使用async/ await,导致相同的错误消息:

System.InvalidOperationException: '超时已过。在从池中获取连接之前超时时间已过。这可能是因为所有池连接都在使用中并且达到了最大池大小。

只是对发生的事情(以及我是如何解决的)的快速概述,希望这将有助于将来的其他人:

查找原因

这一切都发生在一个带有 Dapper 和 SQL Server 的 ASP.NET Core 3.1 Web 项目中,但我确实认为它独立于那种项目。

首先,我有一个中央函数来获取 SQL 连接:

internal async Task<DbConnection> GetConnection()
{
    var r = new SqlConnection(GetConnectionString());
    await r.OpenAsync().ConfigureAwait(false);
    return r;
}
Run Code Online (Sandbox Code Playgroud)

我在几十种方法中使用这个函数,例如这个:

public async Task<List<EmployeeDbModel>> GetAll()
{
    await using var conn = await GetConnection();
    var sql = @"SELECT * FROM Employee";

    var result = await conn.QueryAsync<EmployeeDbModel>(sql);
    return result.ToList();
}
Run Code Online (Sandbox Code Playgroud)

如您所见,我使用的是using不带花括号 ( {, })的 new语句,因此连接的处理是在函数末尾完成的。

尽管如此,我还是收到了关于池中没有更多可用连接的错误。

我开始调试我的应用程序并让它在异常发生时停止。当它停止时,我首先查看了“调用堆栈”窗口,但这仅显示了 System.Data.SqlClient 中的一些位置,对我没有真正的帮助:

在此处输入图片说明

接下来,我查看了Tasks窗口,它提供了更好的帮助:

在此处输入图片说明

GetConnection在“等待”或“计划”状态下,我自己的方法实际上有数千次调用。

在“任务”窗口中双击这样的一行时,它会通过“调用堆栈”窗口向我显示代码中的相关位置。

这有助于我找出这种行为的真正原因。它在下面的代码中(只是为了完整性):

[Route(nameof(LoadEmployees))]
public async Task<IActionResult> LoadEmployees(
    DataSourceLoadOptions loadOption)
{
    var data = await CentralDbRepository.EmployeeRepository.GetAll();

    var list =
        data.Select(async d =>
            {
                var values = await CentralDbRepository.EmployeeRepository.GetAllValuesForEmployee(d);
                return await d.ConvertToListItemViewModel(
                    values,
                    Config,
                    CentralDbRepository);
            })
            .ToListAsync();
    return Json(DataSourceLoader.Load(await list, loadOption));
}
Run Code Online (Sandbox Code Playgroud)

在上面的控制器操作中,我首先调用EmployeeRepository.GetAll()从数据库表“Employee”中获取模型列表。

然后,对于每个返回的模型(即对于结果集的每一行),我再次对EmployeeRepository.GetAllValuesForEmployee(d).

虽然这在性能方面非常糟糕,但在异步上下文中它的行为方式是,它在没有适当释放它们的情况下消耗连接池连接。

解决方案

我通过删除外部 SQL 查询的内循环中的 SQL 查询来解决它。

这应该通过完全省略它来完成,或者如果需要,将它移动到JOIN外部 SQL 查询中的one/multilpe以在单个 SQL 查询中从数据库中获取所有数据。

tl;博士/经验教训

不要在短时间内执行大量 SQL 查询,尤其是在使用async/ 时await


Meh*_*ari 5

您可以通过在连接字符串中指定MinPoolSize=xyz和/或指定最小和最大池大小MaxPoolSize=xyz.然而,这个问题的原因可能是另一回事.

  • 什么是推荐的 MaxPoolSize? (4认同)
  • 可能最好的方法是*不*指定它,除非您有特殊要求并且您知道*特定情况*的合适池大小。 (2认同)

Wim*_*tra 5

在我的一个.NET应用程序中使用某些第三方数据层时,我也遇到过这个问题.问题是该层没有正确关闭连接.

我们抛出了图层并自己创建了一个图层,它总是关闭并处理连接.从那时起,我们不再得到错误.


Dev*_*Zen 5

就我而言,我没有关闭 DataReader 对象。

using (SqlCommand dbCmd = new SqlCommand("*StoredProcedureName*"))
using (dbCmd.Connection = new SqlConnection(WebConfigurationAccess.ConnectionString))
{
    dbCmd.CommandType = CommandType.StoredProcedure;

    //Add parametres
    dbCmd.Parameters.Add(new SqlParameter("@ID", SqlDbType.Int)).Value = ID;

    .....
    .....

    dbCmd.Connection.Open();
    var dr = dbCmd.ExecuteReader(); //created a Data reader here
    dr.Close();    //gotta close the data reader
    //dbCmd.Connection.Close(); //don't need this as 'using' statement should take care of this in its implicit dispose method.
}
Run Code Online (Sandbox Code Playgroud)


小智 5

您也可以尝试解决超时问题:

如果您没有将httpRuntime添加到您的webconfig中,请在<system.web>标记中添加

<sytem.web>
     <httpRuntime maxRequestLength="20000" executionTimeout="999999"/>
</system.web>
Run Code Online (Sandbox Code Playgroud)

像这样修改您的连接字符串;

 <add name="connstring" connectionString="Data Source=DSourceName;Initial Catalog=DBName;Integrated Security=True;Max Pool Size=50000;Pooling=True;" providerName="System.Data.SqlClient" />
Run Code Online (Sandbox Code Playgroud)

最后使用

    try
    {...} 
    catch
    {...} 
    finaly
    {
     connection.close();
    }
Run Code Online (Sandbox Code Playgroud)


Dav*_*son 5

除了发布的解决方案之外。

在处理 1000 页遗留代码时,每个页面都会多次调用公共 GetRS,这是解决该问题的另一种方法:

在现有的通用 DLL 中,我们添加了CommandBehavior.CloseConnection选项:

static public IDataReader GetRS(String Sql)
{
    SqlConnection dbconn = new SqlConnection(DB.GetDBConn());
    dbconn.Open();
    SqlCommand cmd = new SqlCommand(Sql, dbconn);
    return cmd.ExecuteReader(CommandBehavior.CloseConnection);   
}
Run Code Online (Sandbox Code Playgroud)

然后在每个页面中,只要关闭数据读取器,连接也会自动关闭,从而防止连接泄漏。

IDataReader rs = CommonDLL.GetRS("select * from table");
while (rs.Read())
{
    // do something
}
rs.Close();   // this also closes the connection
Run Code Online (Sandbox Code Playgroud)