【发布时间】:2020-09-06 11:38:57
【问题描述】:
EF Core 版本:3.1。
这是我的方法:
public static ILookup<string, int> GetClientCountLookup(DepotContext context, DateRange dateRange)
=> context
.Flows
.Where(e => e.TimeCreated >= dateRange.Start.Date && e.TimeCreated <= dateRange.End.Date)
.GroupBy(e => e.Customer)
.Select(g => new { g.Key, Count = g.Count() })
.ToLookup(k => k.Key, e => e.Count);
所有使用的字段都被索引。
这是生成的查询:
SELECT [f].[Customer] AS [Key], COUNT(*) AS [Count]
FROM [Flows] AS [f]
WHERE ([f].[TimeCreated] >= @__dateRange_Start_Date_0) AND ([f].[TimeCreated] <= @__dateRange_End_Date_1)
GROUP BY [f].[Customer]
当该查询作为 SQL 执行时,执行时间为 100 毫秒。
当该查询在带有ToLookup 方法的代码中使用时 - 执行时间为 3200 毫秒。
更奇怪的是 - EF Core 中的执行时间似乎完全独立于数据样本大小(比方说,根据日期范围,我们可以计算数百或数十万条记录)。
这里到底发生了什么?
我粘贴的查询是 EF Core 发送的真正查询。 我首先粘贴的代码片段在 3200ms 内执行。 然后我使用了准确生成的 SQL 并在 Visual Studio 中作为 SQL 查询执行 - 花了 100 毫秒。
这对我来说没有任何意义。我使用 EF Core 很长一段时间,它似乎表现合理。 大多数查询(简单、无日期范围)都很快,可以立即获取结果(不到 200 毫秒)。
在我的应用程序中,我构建了一个非常庞大的查询,其中包含 4 个多列连接和子查询……猜猜看 - 它在 3200 毫秒内获取 400 行。它还在 3200 毫秒内获取 4000 行。而且当我删除大部分连接时,包括,甚至删除子查询 - 3200ms。或 4000,取决于我的 Internet 或服务器瞬时状态和负载。
这就像不断滞后,我将其精确定位到我粘贴的第一个查询。
我知道ToLookup 方法会导致最终获取所有输入表达式结果,但在我的情况下(真实世界数据) - 正好有 5 行。
结果如下所示:
|------------|-------|
| Key | Count |
|------------|-------|
| Customer 1 | 500 |
| Customer 2 | 50 |
| Customer 3 | 10 |
| Customer 4 | 5 |
| Customer 5 | 1 |
从数据库中获取 5 行需要 4 秒?!这太荒谬了。如果获取了整个表,则对行进行分组和计数——这将加起来。但生成的查询实际上返回 5 行。
这里发生了什么,我错过了什么?
请不要让我提供完整的代码。它是机密的,是我客户项目的一部分,我不允许泄露我客户的商业机密。不是这里,也不是任何其他问题。我知道当你没有我的数据库和整个应用程序时很难理解会发生什么,但这里的问题是纯理论的。要么你知道发生了什么,要么你不知道。就如此容易。不过这个问题很难。
我只能说使用的 RDBMS 是远程运行在 Ubuntu 服务器上的 MS SQL Express。测量的时间是对远程数据库执行代码测试 (NUnit) 或查询的时间,所有这些都是在我的 AMD Ryzen 7 8 核 3.40GHz 处理器上执行的。服务器位于 Azure 上,例如 2 核 I5 2.4GHz 或类似的东西。
【问题讨论】:
-
我建议您在这里阅读并检查您的数据类型是否都按您的预期排列:stackoverflow.com/questions/15767803/…
-
数据库不是获取 4 行,而是获取 566 行并将它们分组。我的猜测是您的问题是确定所需间隔内的行,如果您为 [TimeCreated] 列添加索引,它将得到解决。
-
@MarcGuillot 你的想法和我的想法完全一样——我确保
TimeCreated有索引。我还确保实际提取了 5 行。如何?我试图获取 5000 行并计数(明确地,客户端) - 现在这需要更长的时间。但是不是我的查询,它真的只获取 5 行,我可能偶然解决了这个问题...... -
不清楚您是否只是在查看应用程序的冷启动。如果你不能低于 3200 毫秒,我认为就是这样。
-
@GertArnold 正是这样。这是一个单元测试,但第一个测试是冷启动。我更改了基准代码以在开始测量时间之前执行一个虚拟查询,现在它为查询测量 200 毫秒,考虑到框架开销和所有这些,这是一个很好的时间。似乎开销是恒定的,所以当我采用更复杂的查询返回更多行时 - 它并没有变得相当慢。
标签: sql-server entity-framework-core