在我的例子中还发生了另一个原因,因为使用async/await,导致了相同的错误消息:
系统。InvalidOperationException: '超时。从池中获取连接之前的超时时间。这可能是因为所有池连接都在使用中,且池大小已达到最大。”
只是对发生了什么(以及我是如何解决它的)的一个快速概述,希望这将在未来帮助其他人:
找出原因
这一切都发生在ASP中。NET Core 3.1 web项目与Dapper和SQL Server,但我认为它是独立于这种类型的项目。
首先,我有一个中心函数给我SQL连接:
internal async Task<DbConnection> GetConnection()
{
var r = new SqlConnection(GetConnectionString());
await r.OpenAsync().ConfigureAwait(false);
return r;
}
我在几十个方法中使用这个函数,例如:
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();
}
正如您所看到的,我使用的是没有花括号({,})的new using语句,因此连接的处理是在函数的末尾完成的。
尽管如此,我还是得到了关于池中没有更多可用连接的错误。
我开始调试我的应用程序,并让它在异常发生时停止。当它停止时,我首先看了一下调用堆栈窗口,但这只显示了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));
}
在上面的控制器操作中,我首先调用EmployeeRepository.GetAll()从数据库表“Employee”中获得模型列表。
然后,对于每个返回的模型(即结果集的每一行),我再次对employeerepository . getallvaluesforeemployee (d)进行数据库调用。
虽然这在性能方面非常糟糕,但在异步上下环境中,它的行为方式是占用连接池连接而不适当地释放它们。
解决方案
我通过在外部SQL查询的内部循环中删除SQL查询来解决这个问题。
这应该通过完全省略它来完成,或者如果需要,将它移动到外部SQL查询中的一个/多个lpe join,以便在一个SQL查询中获得数据库中的所有数据。
吸取的教训
不要在短时间内执行大量SQL查询,特别是在使用async/await时。