【问题标题】:ToList slow performance vs foreach slow performanceToList 慢速性能与 foreach 慢速性能
【发布时间】:2012-12-25 16:12:49
【问题描述】:

我正在构建将数据库与 3 个表(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());
    }
}

如你所见,我在每个部分都放了秒表来检查需要这么长时间 这是结果:

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() 当我从数据库加载工作任务时,它会改变 一切。

控制台:

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

.........

现在从数据库加载需要更多时间,4 秒以上。 但内置报告时间约为~1ms 每个内部 foreach 大约需要 10 毫秒

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

知道这里发生了什么吗?

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

2) 为什么没有ToList()、内部foreach 和构建报告会变慢?

我怎样才能让它更快?

谢谢。

【问题讨论】:

  • 对于这类问题,分享更多有关数据库结构(列、索引等)的信息会非常有益。
  • 已编辑问题,希望对您有所帮助:)
  • 据我所知:第一次访问数据时会评估 Linq。在您的情况下,这是“ToList()”或内部 foreach。如果您使用 Linq Join 而不是 Where,您可能会更快。 (请原谅我目前无法提供答案,因为我这里没有开发环境)
  • 奇怪的是,当我从 MySQL 工作台使用相同的参数运行 SQL 查询时,它需要大约 1 秒。

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


【解决方案1】:

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

并且在内部 for-each 循环的每一步中,您的程序都会从​​数据库中请求查询,这太慢了。

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

对不起,我的英语口语很弱 :D

【讨论】:

  • 奇怪的是,当我从 MySQL 工作台使用相同的参数运行 SQL 查询时,它需要大约 1 秒。
【解决方案2】:

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 ... 提供更多建议。但是,如果您采用相同的建议,我相信您也会得到一些改进。

【讨论】:

    【解决方案3】:

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

    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);
    

    并根据需要使用此报告。

    【讨论】:

    • 奇怪的是,当我从 MySQL 工作台使用相同的参数运行 SQL 查询时,它需要大约 1 秒。
    猜你喜欢
    • 1970-01-01
    • 2017-04-30
    • 2018-12-20
    • 1970-01-01
    • 2014-07-21
    • 1970-01-01
    • 2017-02-23
    • 2017-08-23
    • 1970-01-01
    相关资源
    最近更新 更多