22

为什么实体框架会生成嵌套 SQL 查询?

我有这个代码

    var db = new Context();
    var result = db.Network.Where(x => x.ServerID == serverId)
        .OrderBy(x=> x.StartTime)
        .Take(limit);

这会产生这个!(注意双选语句)

SELECT
`Project1`.`Id`, 
`Project1`.`ServerID`, 
`Project1`.`EventId`, 
`Project1`.`StartTime`
FROM (SELECT
`Extent1`.`Id`, 
`Extent1`.`ServerID`, 
`Extent1`.`EventId`, 
`Extent1`.`StartTime`
FROM `Networkes` AS `Extent1`
 WHERE `Extent1`.`ServerID` = @p__linq__0) AS `Project1`
 ORDER BY 
`Project1`.`StartTime` DESC LIMIT 5

我应该改变什么才能产生一个选择语句?我将 MySQL 和实体框架与 Code First 一起使用。

更新

无论传递给OrderBy()方法的参数类型如何,我都会得到相同的结果。

更新 2:定时

Total Time (hh:mm:ss.ms)    05:34:13.000
Average Time (hh:mm:ss.ms)  25:42.000
Max Time (hh:mm:ss.ms)  51:54.000
Count   13
First Seen  Nov 6, 12 19:48:19
Last Seen   Nov 6, 12 20:40:22

原始查询:

SELECT `Project?`.`Id`, `Project?`.`ServerID`, `Project?`.`EventId`, `Project?`.`StartTime` FROM (SELECT `Extent?`.`Id`, `Extent?`.`ServerID`, `Extent?`.`EventId`, `Extent?`.`StartTime`, FROM `Network` AS `Extent?` WHERE `Extent?`.`ServerID` = ?) AS `Project?` ORDER BY `Project?`.`Starttime` DESC LIMIT ?

我使用一个程序从 MySQL 中的当前进程中获取快照。

同时执行了其他查询,但是当我将其更改为仅一个 SELECT 语句时,它永远不会超过一秒钟。也许我还有其他事情正在发生;我在问,因为我不太喜欢 DB...

更新 3:解释语句

生成的实体框架

'1', 'PRIMARY', '<derived2>', 'ALL', NULL, NULL, NULL, NULL, '46', 'Using filesort'
'2', 'DERIVED', 'Extent?', 'ref', 'serveridneventid,serverid', 'serveridneventid', '109', '', '45', 'Using where'

一个班轮

'1', 'SIMPLE', 'network', 'ref', 'serveridneventid,serverid', 'serveridneventid', '109', 'const', '45', 'Using where; Using filesort'

这是来自我的 QA 环境,所以我上面粘贴的时间与 rowcount 解释语句无关。我认为大约有 500,000 条记录与一个服务器 ID 匹配。

解决方案

我从 MySQL 切换到 SQL Server。我不想最终完全重写应用程序层。

4

6 回答 6

8

这是从表达式树逻辑构建查询的最简单方法。通常性能不会成为问题。如果您遇到性能问题,您可以尝试这样的方法来恢复实体:

var results = db.ExecuteStoreQuery<Network>(
    "SELECT Id, ServerID, EventId, StartTime FROM Network WHERE ServerID = @ID", 
    serverId);

results = results.OrderBy(x=> x.StartTime).Take(limit);
于 2012-11-06T20:24:18.610 回答
3

我最初的印象是,这样做实际上会更有效,尽管在针对 MSSQL 服务器进行测试时,无论如何我都得到了不到 1 秒的响应。

使用单个 select 语句,它对所有记录进行排序 ( Order By),然后将它们过滤到您想要查看的集合 ( Where),然后选择前 5 名(Limit 5或者,对我来说,Top 5)。在一张大表上,排序会占用很大一部分时间。使用嵌套语句,它首先将记录过滤到一个子集,然后才对其进行昂贵的排序操作。

编辑:我确实对此进行了测试,但我意识到我的测试中有一个错误导致它无效。测试结果已删除。

于 2012-11-06T19:30:28.097 回答
2

为什么实体框架会产生嵌套查询?简单的答案是因为 Entity Framework 将您的查询表达式分解为一个表达式树,然后使用该表达式树来构建您的查询。树自然生成嵌套查询表达式(即子节点生成查询,父节点生成关于该查询的查询)。

为什么实体框架不简化查询并按照您的意愿编写它?简单的答案是因为可以进入查询生成引擎的工作量有限,虽然现在比早期版本更好,但它并不完美,而且可能永远不会完美。

话虽如此,您将手动编写的查询与在这种情况下生成的查询 EF 之间应该没有显着的速度差异。数据库足够聪明,可以生成在任何一种情况下都首先应用 WHERE 子句的执行计划。

于 2012-11-06T19:49:50.820 回答
1

如果要让 EF 在没有子选择的情况下生成查询,请在查询中使用常量,而不是变量。

我之前创建了自己的 .Where 和所有其他 LINQ 方法,它们首先遍历表达式树并将所有变量、方法调用等转换为 Expression.Constant。这样做只是因为实体框架中的这个问题......

于 2012-11-06T19:27:30.040 回答
1

我只是偶然发现了这篇文章,因为我遇到了同样的问题。我已经花了几天的时间来追踪它,它只是 mysql 中的一个糟糕的查询生成。

我已经在 mysql.com http://bugs.mysql.com/bug.php?id=75272提交了一个错误

总结一下问题:

这个简单的查询

context.products
    .Include(x => x.category)
    .Take(10)
    .ToList();

被翻译成

SELECT
`Limit1`.`C1`, 
`Limit1`.`id`, 
`Limit1`.`name`, 
`Limit1`.`category_id`, 
`Limit1`.`id1`, 
`Limit1`.`name1`
FROM (SELECT
`Extent1`.`id`, 
`Extent1`.`name`, 
`Extent1`.`category_id`, 
`Extent2`.`id` AS `id1`, 
`Extent2`.`name` AS `name1`, 
1 AS `C1`
FROM `products` AS `Extent1` INNER JOIN `categories` AS `Extent2` ON `Extent1`.`category_id` = `Extent2`.`id` LIMIT 10) AS `Limit1`

并且表现相当不错。无论如何,外部查询几乎没用。现在如果我添加一个 OrderBy

context.products
    .Include(x => x.category)
    .OrderBy(x => x.id)
    .Take(10)
    .ToList();

查询更改为

SELECT
`Project1`.`C1`, 
`Project1`.`id`, 
`Project1`.`name`, 
`Project1`.`category_id`, 
`Project1`.`id1`, 
`Project1`.`name1`
FROM (SELECT
`Extent1`.`id`, 
`Extent1`.`name`, 
`Extent1`.`category_id`, 
`Extent2`.`id` AS `id1`, 
`Extent2`.`name` AS `name1`, 
1 AS `C1`
FROM `products` AS `Extent1` INNER JOIN `categories` AS `Extent2` ON `Extent1`.`category_id` = `Extent2`.`id`) AS `Project1`
 ORDER BY 
`Project1`.`id` ASC LIMIT 10

这很糟糕,因为order by它在外部查询中。Theat 意味着 MySQL 必须提取每条记录才能执行 orderby,这会导致using filesort

我验证了 SQL Server(至少是 Comapact)不会为相同的代码生成嵌套查询

SELECT TOP (10) 
[Extent1].[id] AS [id], 
[Extent1].[name] AS [name], 
[Extent1].[category_id] AS [category_id], 
[Extent2].[id] AS [id1], 
[Extent2].[name] AS [name1], 
FROM  [products] AS [Extent1]
LEFT OUTER JOIN [categories] AS [Extent2] ON [Extent1].[category_id] = [Extent2].[id]
ORDER BY [Extent1].[id] ASC
于 2015-01-13T15:09:30.050 回答
-2

实际上,Entity Framework 生成的查询很少丑陋,比 LINQ 2 SQL 少,但仍然很丑陋。

但是,您的数据库引擎很可能会制定所需的执行计划,并且查询会顺利运行。

于 2012-11-06T19:29:42.933 回答