运行 IQueryable.First() 时的性能问题

Posted

技术标签:

【中文标题】运行 IQueryable.First() 时的性能问题【英文标题】:performance issues when running IQueryable.First() 【发布时间】:2017-07-18 12:23:59 【问题描述】:

我正在为一所大学制作一个程序,该程序会吸引活跃的学生,然后生成一份报告供其他流程使用。

其中一个重要功能是查看学生是否已毕业。

如果学生毕业后没有回来,他们不被视为活跃学生。 如果他们毕业后要回来或没有毕业并要再回来一个学期,则认为他们是活跃的。

当一个学生通过 main 函数时,运行它大约需要 5 秒。我发现参与该过程的大部分时间来自此函数中的IQueryable.First()

public static bool ContinuingEducation(string v)

    var TERMSSTU = from t in _DB.TERMs
                        join stu in _DB.STUDENT_TERMS_VIEWs
                        on t.TERMS_ID equals stu.STTR_TERM
                        where v == stu.STTR_STUDENT
                        orderby t.TERM_START_DATE descending
                        select new  startdate = t.TERM_START_DATE ;
    var graduation = from a in _DB.ACAD_CREDENTIALs
                         where v == a.ACAD_PERSON_ID
                         orderby a.ACAD_COMMENCEMENT_DATE ascending
                         select a;

    if (graduation.Count() > 0 && TERMSSTU.Count() > 0)
    
        if (TERMSSTU.First().startdate > graduation.First().ACAD_COMMENCEMENT_DATE) // the problem is here
            return true;
    
    return false;

我不知道为什么在这里需要这么长时间。有没有更好的方法来写出这个函数,所以它更快?

【问题讨论】:

您正在运行 4 个查询。 2 用于计数,然后再 2 用于获取每个查询的第一行。相反,只需使用FirstOrDefault 并检查结果是否为null 以确定是否有多个。 您的问题解决了吗 Franco? 我在下面发布了我的解决方案 【参考方案1】:

您使用Count 效率低下,因为您需要额外查询数据库(一次获取Count,一次获取First)。以下代码更改将消除获取Count 的需要。

变化:

if (graduation.Count() > 0 && TERMSSTU.Count() > 0)

    if (TERMSSTU.First().startdate > graduation.First().ACAD_COMMENCEMENT_DATE) // the problem is here
        return true;

return false;

到:

var grad = graduation.FirstOrDefault();
var term = TERMSSTU.FirstOrDefault()

if (grad == null || term == null)
    return false;

return term.startdate > grad.ACAD_COMMENCEMENT_DATE;

请注意,即使进行此更改后,FirstOrDefault 仍然可能很慢。要解决此问题,您应该运行SQL Trace 以查看正在生成的 SQL。然后看add indexes/优化查询。

【讨论】:

添加到这个答案:FirstCount 慢的原因是First 需要执行order by 以确保它获得第一个元素根据顺序。【参考方案2】:

试用代码

public static bool ContinuingEducation(string v)

     var TERMSSTU = (from t in _DB.TERMs.AsQueryable()
                    join stu in _DB.STUDENT_TERMS_VIEWs.AsQueryable()
                    on t.TERMS_ID equals stu.STTR_TERM
                    where v == stu.STTR_STUDENT
                    orderby t.TERM_START_DATE descending
                    select new  startdate = t.TERM_START_DATE ).FirstOrDefault();
     var graduation = (from a in _DB.ACAD_CREDENTIALs.AsQueryable()
                     where v == a.ACAD_PERSON_ID
                     orderby a.ACAD_COMMENCEMENT_DATE ascending
                     select a).FirstOrDefault();

    if (graduation==null  || TERMSSTU==null)
         return false;

     return TERMSSTU.startdate >graduation.ACAD_COMMENCEMENT_DATE;

【讨论】:

【参考方案3】:

性能问题不在First 调用中,而是在您的查询中。当您调用 First 时会对其进行评估。

您应该重写代码以使用更少的查询:您可以在 v 参数上加入两个查询,然后获取结果并检查它的日期。它将产生一个查询。

【讨论】:

【参考方案4】:

为了提高效率,我最好的办法是在我的程序开始时查询这些记录,这样所有记录都在内存中,这使得查询程序变得轻而易举。

    我创建了一个新类来捕获这两个查询

    公开课 TERMSTU 公共日期时间?开始日期 得到;放; 公共字符串 ID 获取;放;

    公开课毕业 公共日期时间? ACAD_COMMENCEMENT_DATE 获取;放; 公共字符串 ID 获取;放; 公开课报告 私人清单 TERMSSTUS; 私人名单毕业; 公共报告(ColleagueDataContext _DB) var TERMSSTU = 来自 _DB.TERMs 中的 t 在 _DB.STUDENT_TERMS_VIEWs 中加入 stu 在 t.TERMS_ID 上等于 stu.STTR_TERM orderby t.TERM_START_DATE 降序 选择新的 startdate = t.TERM_START_DATE,id = stu.STTR_STUDENT ; TERMSSTUS = 新列表(); foreach(TERMSSTU 中的变量 i) TERMSSTUS.Add(new TERMSTU() Id = i.id, startdate = i.startdate );

        var graduation = from a in _DB.ACAD_CREDENTIALs
                         where a.ACAD_COMMENCEMENT_DATE != null
                         where a.ACAD_COMMENCEMENT_DATE < DateTime.Today
                         orderby a.ACAD_COMMENCEMENT_DATE descending
                         select a;
    
        GRADUATIONS = new List<Graduation>();
        foreach (var i in graduation)
        
            GRADUATIONS.Add(new Graduation()  Id = i.ACAD_PERSON_ID, ACAD_COMMENCEMENT_DATE = i.ACAD_COMMENCEMENT_DATE );
        
    
    public List<TERMSTU> givestu()
    
        return TERMSSTUS;
    
    public List<Graduation> givegrad()
    
        return GRADUATIONS;
    
    

    我修改了函数以获取两个查询

    public static bool ContinuingEducation(string v, List<TERMSTU> t, List<Graduation> g)
    
        var term = (from stu in t where stu.Id == v select stu).FirstOrDefault();
        var grad = (from gradu in g where gradu.Id == v select gradu).FirstOrDefault();
        return term.startdate > grad.ACAD_COMMENCEMENT_DATE.Value.AddDays(-14);
    
    

这种方法要写的代码比较多,但是当程序需要遍历 3000 条左右的记录时,它会节省很多时间。

【讨论】:

每个stu 是否有唯一的Id?每个gradu 是否都有唯一的Id?如果是这样,您应该将它们存储在 Dictionary 而不是 List 中,以戏剧性地加快速度。 在您的ContinuingEducation 方法中,您还应该考虑如果termgrad 为空,您的代码应该做什么。原样的代码会抛出异常。

以上是关于运行 IQueryable.First() 时的性能问题的主要内容,如果未能解决你的问题,请参考以下文章

Django 形式的性

思维及其目的性

记一次对 Laravel-permission 项目的性能优化

矩阵——条件数与方程组的性态

测者的性测试手册:SWAP的监控

基于web网站项目的性能测试结果分析