性能调整实体框架查询

Posted

技术标签:

【中文标题】性能调整实体框架查询【英文标题】:Performance tuning Entity Framework queries 【发布时间】:2016-08-29 21:44:19 【问题描述】:

所以我开发了一个查询数据库的仪表板。该数据库中存储了我们拥有的网站的谷歌分析数据。

我正在使用带有 Telerik 控件/小部件的 ASP.NET MVC 5、EF、Linq。控制器实例化一个服务层,其中我有我的数据库上下文和业务逻辑。每个 svc.method() 都与一个特定的结果集相关,然后我将其打包到 VM 中,以便解包到视图中的小部件中。

目前,谷歌浏览器网络标签的响应时间为 5.6 秒。我已经说明了向您展示我的方法的 8 种方法之一。

我的问题是;如何提高性能以使页面加载更快?使每种方法异步会改善它吗?

提前感谢您提供的任何建议。

控制器:

    public ActionResult WebStats()
    
        //other code removed for brevity

        //Service layer where the db is queried and the business logic is performend
        WebStatsService svc = new WebStatsService();

        //view model 
    WebStatsViewModel vm = new WebStatsViewModel();

        vm.PageViews = svc.GetPageViews(vm);
        vm.UniquePageViews = svc.GetUniquePageViews(vm);
        vm.UserRatioByCountry = svc.GetUserRatioByCountry(vm);
        vm.PageViewsByCountry = svc.GetPageViewsByCountry(vm);
        vm.TopTenHealthCenters = svc.GetTopTenHealthCenters(vm);
        vm.UserTypeRatio = svc.GetUserTypeRatio(vm);
        vm.TopTenHealthCentersByDateRange = svc.GetTopTenHealthCentersByDateRange(vm);
        vm.ReferralSources = svc.GetTopTenReferralSources(vm);//Get top 10 referral paths


        return View(vm);
    

服务:

    public List<PageViews> GetPageViews(WebStatsViewModel vm)
    
        using (ApplicationDbContext db = new ApplicationDbContext())
        
            List<PageViews> pageViewStats = new List<PageViews>();

            var results = db.PageStats.Where(x => (vm.CMS.Equals("All") || x.Source.Equals(vm.CMS))
                                               && (vm.HealthCenter.Equals("All") || x.HealthSectionName.Equals(vm.HealthCenter))
                                               && (vm.Country.Equals("All") || x.Country.Equals(vm.Country))
                                               && (vm.City.Equals("All") || x.City.Equals(vm.City))
                                               && (x.Date >= vm.StartDate)
                                               && (x.Date <= vm.EndDate)
                                            ).Select(x => new
                                            
                                                Date = x.Date,
                                                Total = x.PageViews
                                            ).ToList();

            var distinctDate = results.OrderBy(x => x.Date).Select(x => x.Date).Distinct();

            foreach (var date in distinctDate)
            
                PageViews pageViewStat = new PageViews();

                pageViewStat.Date = date.Value.ToShortDateString();
                pageViewStat.Total = results.Where(x => x.Date == date).Sum(x => x.Total);

                pageViewStats.Add(pageViewStat);
            

            return pageViewStats;
        
    

【问题讨论】:

这个问题对于这个网站来说实在是太宽泛了。您可能会从使其异步中获得一些好处,但您的里程可能会有所不同。我们也许可以帮助您在此处调整一个查询的性能,但不能帮助您调整总体布局/性能。 感谢@DavidG。我正在考虑改善查询响应时间,视图性能是另一回事。也就是说,我正在寻找有关不同方法的建议,例如使每个小部件成为局部视图会提高响应性能吗?我正在寻找有关不同策略的建议。 建议只是意见,这是不适合 SO 的另一个原因。遗憾的是,我很想通过 AJAX 调用将数据加载到您的视图中,这样页面将在检索每个部分时显示。 【参考方案1】:

以下是一些关于 EF 查询的提示:

(1) 避免在动态过滤器中混合常量和实际谓词,如下所示:

(vm.CMS.Equals("All") || x.Source.Equals(vm.CMS))

它可能看起来很简洁,但会生成糟糕且低效的 SQL。相反,使用if 语句并链接Where

// Base query including static filters
var query = db.PageStats.AsQueryable();
// Apply dynamic filters
if (!vm.CMS.Equals("All"))
    query = query.Where(x => x.Source.Equals(vm.CMS));
// ...
// The rest of the query
query = query.Select(...

(2) 尝试从 SQL 查询返回尽可能少的数据。

例如,您的查询正在使用(Date, Total) 对填充列表,然后您手动(但效率不高)将其按Date 分组并采用Sum(Total)。相反,您可以让 EF 查询直接返回该分组/聚合数据。

将所有这些应用到您的示例中会导致如下结果:

using (ApplicationDbContext db = new ApplicationDbContext())

    var query = db.PageStats
        .Where(x => x.Date >= vm.StartDate && x.Date <= vm.EndDate);

    if (!vm.CMS.Equals("All"))
        query = query.Where(x => x.Source.Equals(vm.CMS));
    if (!vm.HealthCenter.Equals("All"))
        query = query.Where(x => x.HealthSectionName.Equals(vm.HealthCenter));
    if (!vm.Country.Equals("All"))
        query = query.Where(x => x.Country.Equals(vm.Country));
    if (!vm.City.Equals("All"))
        query = query.Where(x => x.City.Equals(vm.City));

    query = query
        .GroupBy(x => x.Date)
        .Select(g => new
        
            Date = g.Key,
            Total = g.Sum(x => x.PageViews)
        )
        .OrderBy(x => x.Date);

    var pageViewStats = query
        .AsEnumerable() // SQL query ends here
        .Select(x => new PageViews
        
            Date = x.Date.Value.ToShortDateString(),     
            Total = x.Total
        )
        .ToList();

    return pageViewStats;

您可以尝试将性能与原版进行比较。

(注意:对于这个特定的查询,我们需要使用两个投影——一个在 SQL 查询中临时,一个在内存中查询。这是因为需要 ToShortDateString() 方法,该方法不支持 SQL 查询. 在大多数情况下,SQL 查询中的单个最终投影就足够了。)

【讨论】:

谢谢,伊万。去展示我仍然需要如何提高我的 linq 能力......那么查询直到 .AsEnumerable() 才执行吗?好奇您对 SQL 执行的建议是如何被推迟的以及何时执行。 其实是通过ToList调用来执行的。 AsEnumerable 只是将上下文从IQueryable(因此在查询提供程序的控制下)切换到IEnumerable(即LINQ to Objects - 基于Enumerable 类中包含的扩展方法)。只要您继续在IQueryable 上链接方法,它们就会被转换为 SQL。 IEnumerable 方法在内存中执行。【参考方案2】:

一些提示:

    索引 - 出现在 select 操作的 where 子句中的索引列,使用 SQL profiler 检测“表扫描”操作并添加索引以避免它们(将它们替换为索引搜索或聚集索引搜索)

    缓存 - 将来自 SQL 探查器的跟踪存储到数据库中的表(SQL 探查器可以做到)并通过 sql 文本对 SQL 命令进行分组,这可能会显示一些可以通过缓存避免的重复选择

    Glimpse - 可以计算每个 Web 请求的 SQL 命令,如果 Web 应用程序尚未优化,这个数字可能会令人惊讶。 Glimpse 可以了解更多信息,例如 Web 请求的总时间中有多少时间花费在服务器上,以及 Web 浏览器渲染页面的时间有多少。

    作为最后的手段,为最暴露的查询编写自己的 SQL

【讨论】:

以上是关于性能调整实体框架查询的主要内容,如果未能解决你的问题,请参考以下文章

如何对此查询进行性能调整

针对 DELETE 查询的 MySQL 性能调整

在 Snowflake 中,调整现有仓库的大小是不是有助于提高正在运行的查询的性能?

MySQL关于财政年度的查询性能调整

如何对Oracle sql 进行性能优化的调整

带有相关子查询的 While 循环的 SQL Server 性能调整