簡體   English   中英

SQL Azure與內部部署超時問題 - EF

[英]SQL Azure vs. On-Premises Timeout Issue - EF

我正在編寫一份報告,它與我們的本地數據庫運行良好(剛剛從PROD刷新)。 但是,當我將站點部署到Azure時,我在執行期間獲得了SQL Timeout。 如果我將我的開發實例指向SQL Azure實例,我也會得到超時。

目標:輸出已在搜索范圍內創建活動的客戶列表,以及何時找到該客戶,獲取有關該客戶有關策略等的其他信息。為簡潔起見,我刪除了以下一些屬性(盡我所能)...

UPDATE

經過大量的反復試驗,只要不執行此代碼塊,我就可以使整個查詢在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();

我不能給你一個明確的答案,但我建議通過以下方式解決問題:

  1. 執行此代碼時,在本地運行SQL事件探查器,並查看生成和運行的SQL。 查看每個查詢的查詢執行計划,並查找表掃描和其他慢速操作。 根據需要添加索引。
  2. 檢查你的lambdas,看看那些無法輕易翻譯成SQL的東西。 您可能會將表的內容拉入內存並在結果上運行lambdas,這將非常慢。 改變你的lambda或考慮編寫原始SQL。
  3. Azure數據庫是否與本地數據庫相同? 如果沒有,請在本地提取數據,以便您的本地系統具有指示性。
  4. 刪除部分(即CustomerGroup,然后是CurrentDisposition,然后是ExistingSale,然后是NewSale),並在刪除最后一部分后查看是否有顯着的性能提升。 專注於最后刪除的部分。

看着這條線本身:

  1. 你在第4行使用“.Count()> 0”。使用“.Any()”代替,因為前者遍歷數據庫中的每一行,當你只想知道是否至少有一行時,可以獲得准確的計數滿足要求。
  2. 確保where子句中引用的字段具有索引,例如IsReportable

簡答:使用記憶。

答案很長:

由於糟糕的維護計划或有限的硬件,在一個大塊中運行此查詢是導致它在Azure上失敗的原因。 即使不是這種情況,由於您正在使用的所有導航屬性,此查詢將生成數量驚人的連接。 這里的答案是將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();

如果沒有看到實際的表定義,特別是活動實體上的索引和外鍵,很難提出任何建議。

據我所知,Activity(CustomerId,ActivityTypeId,DueDateTime,DispositionId)。 如果這是標准的倉儲表(DateTime,ClientId,Activity),我建議如下:

  1. 如果活動數量相當小,則強制使用CONTAINS

    var activities = db.Activities.Where(x => x.IsReportable).ToList();

    ...

    .Where(b => activities.Contains(b.Activity))

您甚至可以通過指定您想要ActivityId來幫助優化器。

  1. Activitiy實體的索引應該是最新的。 對於這個特定的查詢我建議(CustomerId,ActivityId,DueDateTime DESC)

  2. 預處理表,我的水晶球告訴我它是字典表。

  3. 對於類似的任務,為了避免經常點擊Activity表,我創建了另一個小表(CustomerId,LastActivity,LastVAlue)並在狀態發生變化時更新了它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM