ToList性能降低与性能缓慢相比

时间:2012-12-25 16:12:49

标签: performance linq c#-4.0 entity-framework-4 tolist

我正在构建使用带有3个表的DataBase的程序(Worker,Task,TaskStep) 我有一个方法,为特定工人获取日期和构建报告 任务和特定日期的步骤。

数据库结构如下:

MySQL 5.2

Worker表格列:

workerID(VARCHAR(45)),
name(VARCHAR(45)),
age(int),
...

Tasks表格列:

TaskID(VARCHAR(45)),
description(VARCHAR(45)),
date(DATE),
...

TaskSteps表格列:

TaskStepID(VARCHAR(45)),
description(VARCHAR(45)),
date(DATE),
...

没有对任何表格进行索引

问题是,它非常非常慢!! (~20秒)

以下是代码:

using WorkerDailyReport = Dictionary<task, IEnumerable<taskStep>>;

private void Buildreport(DateTime date)
{
    var report = new WorkerDailyReport();    

    // Load from DB
    var sw = new Stopwatch();
    sw.Start();

    var startOfDay  = date.Date;
    var endOfDay    = startOfDay.AddDays(1);
    var db          = new WorkEntities();

    const string    workerID   = "80900855";

    IEnumerable<task> _tasks = db.task
                    .Where(ta =>    ta.date     >= startOfDay   &&
                                    ta.date     <  endOfDay     &&
                                    ta.workerID == workerID)
                    .ToList();

    sw.Stop();
    Console.WriteLine("Load From DB time - " + sw.Elapsed + 
                      ", Count - "           + _tasks.Count());   

    // Build the report
    sw.Restart();

    foreach (var t in _tasks)
    {
        var ts = db.taskStep.Where(s => s.taskID == task.taskID);

        report.Add(t, ts);
    }

    sw.Stop();
    Console.WriteLine("Build report time - " + sw.Elapsed);

    // Do somthing with the report
    foreach (var t in report)
    {
        sw.Restart();

        foreach (var subNode in t.Value)
        {
            // Do somthing..
        }

        Console.WriteLine("Do somthing time - " + sw.Elapsed + 
                          ", Count - " + t.Value.Count());
    }
}

你可以看到我把StopWatch放在每个部分来检查需要这么长时间 这就是结果:

1)

如果我运行上面的代码:

控制台:

Load From DB time - 00:00:00.0013774, Count - 577

Build report time - 00:00:03.6305722

Do somthing time - 00:00:07.7573754, Count - 21

Do somthing time - 00:00:08.2811928, Count - 11

Do somthing time - 00:00:07.8715531, Count - 14

Do somthing time - 00:00:08.0430597, Count - 0

Do somthing time - 00:00:07.7867790, Count - 9

Do somthing time - 00:00:07.3485209, Count - 39

.........

内部的foreach运行大约需要7-9!秒不再运行 40记录。

2)

如果我只更改了一件事,请在第一次查询后添加.ToList() 当我从数据库加载工作任务时它会改变 everithing。

控制台:

Load From DB time - 00:00:04.3568445, Count - 577

Build report time - 00:00:00.0018535

Do somthing time - 00:00:00.0191099, Count - 21

Do somthing time - 00:00:00.0144895, Count - 11

Do somthing time - 00:00:00.0150208, Count - 14

Do somthing time - 00:00:00.0179021, Count - 0

Do somthing time - 00:00:00.0151372, Count - 9

Do somthing time - 00:00:00.0155703, Count - 39

.........

现在,DataBase的负载需要更多时间,4秒以上。 但是Built报告的时间约为1ms 每个内部的foreach需要~10ms

第一种方式是不可能的(577 * ~8秒)和seconde选项 也很慢,我看不到你。

知道这里发生了什么吗?

1)为什么ToList()这么慢?

2)为什么没有ToList(),内部foreach和Build报告正在放缓?

如何让它更快?

日Thnx。

3 个答案:

答案 0 :(得分:1)

当你不使用.ToList()时,在你第一次需要从数据库中获取数据之前,C#不会从数据库加载你的数据,这是因为实体框架中的延迟加载。

并且在内部for-each循环的每个步骤中,您的程序从数据库请求查询,这是非常慢的。

但是,当你使用.ToList()时,你立即运行查询并首先获取所有记录,这很慢。然后,在内部for-each循环中,程序将所有记录都记录在内存中。

请原谅我英语口语中的弱势:D

答案 1 :(得分:0)

LINQ ToList()总是立即评估序列 - 在您的情况下,SQL查询数据库。

在第一个实例中,您获得了Load From DB time - 00:00:00.0013774, Count - 577 - 因为您没有运行SQL查询,所以很快。但是,该查询稍后运行了 - 这就是您Build report time - 00:00:03.6305722)的原因。

在第二个实例中,立即强制ToList()强制评估查询(执行SQL),这就是为什么你有以下时间:

  • Load From DB time - 00:00:04.3568445, Count - 577 - 对数据库的SQL查询(
  • Build report time - 00:00:00.0018535 - 对已经在内存中的数据进行操作(快速

有趣的是,您的返回 577 项目的查询超过3秒。这可能是因为其中一个表上缺少索引。

请记住,如果表上没有索引,数据库系统需要执行全表扫描以查找满足以下条件的所有项目:

.Where(ta => ta.date >= startOfDay &&
             ta.date < endOfDay &&
             ta.workerID == workerID)

如果Tasks表中的项目数量增长,您的查询将越来越长

所以我强烈建议在Tasks.dateTasks.workerId列上创建索引。这应该可以改善初始查询时间(假设您的数据库连接速度很快,即您没有连接到部署在海洋上的数据库)。

顺便说一句,不要在所有表列上创建索引(仅限在查询条件中使用的索引)。这可能会降低插入操作的速度并增加数据库大小。

不幸的是,我不能在Do somthing time ...上提供更多建议,因为您没有提供代码。但是,如果您应用相同的建议,我相信您也会得到一些改进。

答案 2 :(得分:0)

要提高性能,您应该使用一个查询从所有表中获取数据:

var _joinedTasks = db.task.Where(ta =>    ta.date     >= startOfDay   &&
                                    ta.date     <  endOfDay     &&
                                    ta.workerID == workerID)
                    .Join(db.taskStep, t => t.taskID, ts=>ts.taskID, (t, ts) => new {t, ts})
                    .GroupBy(g => g.t, v=>v.ts).AsEnumerable();

然后你可以把它添加到字典中:

var report = _joinedTasks.ToDictionary(g=>g.Key);

并按照您的意愿使用此报告。