SQL Azure与内部部署超时问题 - EF

时间:2014-10-25 23:48:28

标签: c# sql entity-framework azure azure-sql-database

我正在处理一份报告,该报告与我们的本地数据库(刚刚从PROD刷新)运行良好。但是,当我将站点部署到Azure时,我在执行期间获得了SQL Timeout。如果我将我的开发实例指向SQL Azure实例,我也会得到超时。

目标:要输出已在搜索范围内创建活动的客户列表,以及何时找到该客户,请获取有关该客户的有关政策等的其他信息。我已删除了下面的一些属性为了简洁(尽我所能)......

更新

经过大量的反复试验,只要不执行此代码块,我就可以在1000MS内完全一致地运行整个查询。

CurrentStatus = a.Activities
                                .Where(b => b.ActivityType.IsReportable)
                                .OrderByDescending(b => b.DueDateTime)
                                .Select(b => b.Status.Name)
                                .FirstOrDefault(),

有了这些代码,事情开始变得混乱。我认为Where子句是其中很重要的一部分:.Where(b => b.ActivityType.IsReportable)。获取状态名称的最佳方法是什么?

现有代码

有关为什么SQL Azure会超时而内部部署的任何想法都会在不到100MS的时间内解决这个问题?

return db.Customers
    .Where(a => a.Activities.Where(
        b => b.CreatedDateTime >= search.BeginDateCreated
        && b.CreatedDateTime <= search.EndDateCreated).Count() > 0)
    .Where(a => a.CustomerGroup.Any(d => d.GroupId== search.GroupId))
    .Select(a => new CustomCustomerReport
    {
        CustomerId = a.Id,
        Manager = a.Manager.Name,
        Customer = a.FirstName + " " + a.LastName,
        ContactSource= a.ContactSource!= null ? a.ContactSource.Name : "Unknown",
        ContactDate = a.DateCreated,

        NewSale = a.Sales
            .Where(p => p.Employee.IsActive)
            .OrderByDescending(p => p.DateCreated)
            .Select(p => new PolicyViewModel
            {
                //MISC PROPERTIES
            }).FirstOrDefault(),

        ExistingSale = a.Sales
            .Where(p => p.CancellationDate == null || p.CancellationDate <= myDate)
            .Where(p => p.SaleDate < myDate)
            .OrderByDescending(p => p.DateCreated)
            .Select(p => new SalesViewModel
            {
                //MISC PROPERTIES
            }).FirstOrDefault(),

        CurrentStatus = a.Activities
                            .Where(b => b.ActivityType.IsReportable)
                            .OrderByDescending(b => b.DueDateTime)
                            .Select(b => b.Disposition.Name)
                            .FirstOrDefault(),

        CustomerGroup = a.CustomerGroup
                            .Where(cd => cd.GroupId == search.GroupId)
                            .Select(cd => new GroupViewModel
                            {
                                //MISC PROPERTIES
                            }).FirstOrDefault()
    }).ToList();

3 个答案:

答案 0 :(得分:4)

我不能给你一个明确的答案,但我建议通过以下方式解决问题:

  1. 执行此代码时在本地运行SQL事件探查器,并查看生成和运行的SQL。查看每个查询的查询执行计划,并查找表扫描和其他慢速操作。根据需要添加索引。
  2. 检查您的lambdas以查找无法轻松转换为SQL的内容。您可能会将表的内容拉入内存并在结果上运行lambdas,这将非常慢。改变你的lambda或考虑编写原始SQL。
  3. Azure数据库是否与本地数据库相同?如果没有,请在本地提取数据,以便本地系统显示。
  4. 删除部分(即CustomerGroup,然后是CurrentDisposition,然后是ExistingSale,然后是NewSale),并在删除最后一部分后查看是否有显着的性能提升。专注于最后删除的部分。
  5. 看看这条线本身:

    1. 您在第4行使用“.Count()&gt; 0”。使用“.Any()”代替,因为前者会遍历数据库中的每一行,以便在您只想知道至少有一行满足要求。
    2. 确保where子句中引用的字段具有索引,例如IsReportable

答案 1 :(得分:1)

简短回答:使用记忆。

答案很长:

由于糟糕的维护计划或有限的硬件,在一个大块中运行此查询是导致它在Azure上失败的原因。即使情况并非如此,由于您使用的所有导航属性,此查询将生成数量惊人的连接。这里的答案是将Azure 可以运行的较小部分分解。我将尝试将您的查询重写为使用.NET应用程序内存的多个更小,更易于摘要的查询。请耐心等待我(或多或少)对您的业务逻辑/数据库架构进行有根据的猜测并相应地重写查询。很抱歉使用LINQ的查询表单,但我发现joingroup by等内容在该表单中更具可读性。

var activityFilterCustomerIds = db.Activities
    .Where(a => 
        a.CreatedDateTime >= search.BeginDateCreated &&
        a.CreatedDateTime <= search.EndDateCreated)
    .Select(a => a.CustomerId)
    .Distinct()
    .ToList();

var groupFilterCustomerIds = db.CustomerGroup
    .Where(g => g.GroupId = search.GroupId)
    .Select(g => g.CustomerId)
    .Distinct()
    .ToList();

var customers = db.Customers
    .AsNoTracking()
    .Where(c => 
        activityFilterCustomerIds.Contains(c.Id) &&
        groupFilterCustomerIds.Contains(c.Id))
    .ToList();

var customerIds = customers.Select(x => x.Id).ToList();

var newSales =
    (from s in db.Sales
    where customerIds.Contains(s.CustomerId)
    && s.Employee.IsActive
    group s by s.CustomerId into grouped
    select new
    {
        CustomerId = grouped.Key,
        Sale = grouped
            .OrderByDescending(x => x.DateCreated)
            .Select(new PolicyViewModel
            {
                // properties
            })
            .FirstOrDefault()
    }).ToList();


var existingSales = 
    (from s in db.Sales
    where customerIds.Contains(s.CustomerId)
    && (s.CancellationDate == null || s.CancellationDate <= myDate)
    && s.SaleDate < myDate
    group s by s.CustomerId into grouped
    select new
    {
        CustomerId = grouped.Key,
        Sale = grouped
            .OrderByDescending(x => x.DateCreated)
            .Select(new SalesViewModel
            {
                // properties
            })
            .FirstOrDefault()
    }).ToList();

var currentStatuses = 
    (from a in db.Activities.AsNoTracking()
    where customerIds.Contains(a.CustomerId)
    && a.ActivityType.IsReportable
    group a by a.CustomerId into grouped
    select new
    {
        CustomerId = grouped.Key,
        Status = grouped
            .OrderByDescending(x => x.DueDateTime)
            .Select(x => x.Disposition.Name)
            .FirstOrDefault()
    }).ToList();

var customerGroups =
    (from cg in db.CustomerGroups
    where cg.GroupId == search.GroupId
    group cg by cg.CustomerId into grouped
    select new
    {
        CustomerId = grouped.Key,
        Group = grouped
            .Select(x =>
                new GroupViewModel
                {
                    // ...
                })
            .FirstOrDefault()
    }).ToList();

    return customers
        .Select(c =>
            new CustomCustomerReport
            {
                // ... simple props
                // ...
                // ...
                NewSale = newSales
                    .Where(s => s.CustomerId == c.Id)
                    .Select(x => x.Sale)
                    .FirstOrDefault(),
                ExistingSale = existingSales
                    .Where(s => s.CustomerId == c.Id)
                    .Select(x => x.Sale)
                    .FirstOrDefault(),
                CurrentStatus = currentStatuses
                    .Where(s => s.CustomerId == c.Id)
                    .Select(x => x.Status)
                    .FirstOrDefault(),
                CustomerGroup = customerGroups
                    .Where(s => s.CustomerId == c.Id)
                    .Select(x => x.Group)
                    .FirstOrDefault(),
            })
        .ToList();

答案 2 :(得分:0)

很难在没有看到实际表定义的情况下提出任何建议,特别是活动实体上的索引和外键。

据我所知,Activity(CustomerId,ActivityTypeId,DueDateTime,DispositionId)。如果这是标准的仓储表(DateTime,ClientId,Activity),我建议如下:

  1. 如果活动数量相当小,则强制使用CONTAINS

    var activities = db.Activities.Where(x =&gt; x.IsReportable)。ToList();

    ...

    .Where(b =&gt; activities.Contains(b.Activity))

  2. 您甚至可以通过指定您想要ActivityId来帮助优化器。

    1. Activitiy实体的索引应该是最新的。对于这个特定的查询,我建议(CustomerId,ActivityId,DueDateTime DESC)

    2. 预处理处理表,我的水晶球告诉我它是字典表。

    3. 对于类似的任务,为了避免经常点击Activity表,我创建了另一个小表(CustomerId,LastActivity,LastVAlue),并在状态发生变化时对其进行了更新。