我可以异步创建多个DBConnections吗?

aye*_*ers 5 c# sql-server asynchronous dapper

我正在尝试提高复杂数据库读取操作的性能.我发现一些代码在有限的测试中比以前尝试使用各种技术(包括手动调整的存储过程)执行得更快.它正在使用Dapper,但Dapper并不是主要关注点.

public IEnumerable<Order> GetOpenOrders(Guid vendorId)
{
    var tasks = GetAllOrders(vendorId)
        .Where(order => !order.IsCancelled)
        .Select(async order => await GetLineItems(order))
        .Select(async order =>
        {
            var result = (await order);
            return result.GetBalance() > 0M ? result : null;
        })
        .Select(async order => await PopulateName(await order))
        .Select(async order => await PopulateAddress(await order))
        .ToList();
    Task.WaitAll(tasks.ToArray<Task>());
    return tasks.Select(t => t.Result);
}

private IDbConnection CreateConnection()
{
    return new SqlConnection("...");
}

private IEnumerable<Order> GetAllOrders(Guid vendorId)
{
    using (var db = CreateConnection())
    {
        return db.Query<Order>("...");
    }
}

private async Task<Order> GetLineItems(Order order)
{
    using (var db = CreateConnection())
    {
        var lineItems = await db.QueryAsync<LineItem>("...");
        order.LineItems = await Task.WhenAll(lineItems.Select(async li => await GetPayments(li)));
        return order;
    }
}

private async Task<LineItem> GetPayments(LineItem lineItem)
{
    using (var db = CreateConnection())
    {
        lineItem.Payments = await db.QueryAsync<Payment>("...");
        return lineItem;
    }
}

private async Task<Order> PopulateName(Order order)
{
    using (var db = CreateConnection())
    {
        order.Name = (await db.QueryAsync<string>("...")).FirstOrDefault();
        return order;
    }
}

private async Task<Order> PopulateAddress(Order order)
{
    using (var db = CreateConnection())
    {
        order.Address = (await db.QueryAsync<string>("...")).FirstOrDefault();
        return order;
    }
}
Run Code Online (Sandbox Code Playgroud)

这有点简化,但我希望它突出了我的主要问题:

  • 这段代码是个好主意吗?

我知道通过重复使用相同的连接可以使其更安全,但创建许多连接会使我的测试速度提高一个数量级.我还测试/计算了数据库本身的并发连接数,我看到有数百个语句同时运行.

一些相关问题:

  • 我应该使用更多的异步(例如:CreateConnection(),GetAllOrders)还是更少?
  • 在将这种代码投入生产之前,我可以/应该做什么样的测试?
  • 是否存在可以产生类似性能但需要更少连接的替代策略?

Mat*_*int 8

您的代码最大的问题是您从数据库中获取的数据多于实际需要满足查询的数据.这被称为无关取出.

Dapper非常棒,但与Entity Framework和其他解决方案不同,它不是LINQ提供商.您必须在SQL中表达您的全部查询,包括该WHERE子句.Dapper只是帮助您将其实现为对象.它返回IEnumerable<T>,而不是IQueryable<T>.

所以你的代码:

GetAllOrders(vendorId)
    .Where(order => !order.IsCancelled)
Run Code Online (Sandbox Code Playgroud)

实际上请求数据库中的所有订单 - 而不仅仅是未删除的订单.过滤器发生在内存中,然后发生.

同样:

order.Name = (await db.QueryAsync<string>("...")).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)

...查询的更好的包括SELECT TOP 1,或者你真正能拿到的所有项目回来,只是扔掉所有,但第一个项目.

此外,请考虑您正在进行许多较小的调用以填充订单的每个细分.对于每个订单,您有3个额外的查询,其他N行.这是一种常见的反模式,称为SELECT N + 1.它总是更好地表达您的查询的全部为"矮胖"的操作,而不是发出许多繁琐的查询到数据库中.这也被描述为繁琐的I/O反模式.

关于异步问题 - 虽然并行进行多个数据库调用没有任何内在错误,但这并不是你在这里做的.既然你正在等待沿途的每一步,你仍然在连续地做事.

好吧,至少你是按顺序为每个订单做的.你在外循环中获得了一些并行性.但所有内在的东西本质上是连续的.在Task.WaitAll将阻塞,直到所有的外任务(过滤每一个顺序)是完整的.

另一个问题是,当您GetOpenOrders首先呼叫时,您不在异步上下文中.除非您在堆栈中一直向上和向下同步,否则无法实现async/await的真正好处.我还建议你在第9频道观看这部视频系列.

我的建议是:

  • 确定运行所需的完整查询以从数据库中检索所有数据,但不超过实际需要的数据.
  • 在Dapper中执行该查询.使用Query如果在同步上下文(是IEnumerable<Order> GetOpenOrders),或使用QueryAsync,如果你在一个异步方面是(async Task<IEnumerable<Order>> GetOpenOrdersAsync).不要尝试使用非异步上下文中的异步查询.
  • 使用Dapper的多映射功能从单个查询中检索多个对象.

  • 很好的答案 (2认同)