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

Posted

技术标签:

【中文标题】为啥实体框架会生成嵌套 SQL 查询?【英文标题】:Why does the Entity Framework generate nested SQL queries?为什么实体框架会生成嵌套 SQL 查询? 【发布时间】:2012-10-26 19:16:24 【问题描述】:

为什么实体框架会生成嵌套的 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

我应该更改哪些内容才能生成一个 select 语句?我正在使用 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。我不想最终完全重写应用层。

【问题讨论】:

首先,LINQ To SQL 和实体框架是不同的东西。其次,为什么你认为这个查询不好?您是否进行了性能分析或至少运行了解释? 是的,我用纯 SQL 手动构建它们。有 2800 万条记录,毫秒 vs 分钟 @SimonEdström - 该查询没有任何问题,实际上它应该这样做。您不希望对 2800 万条记录进行排序,而是希望选择受 where 子句约束的子集,然后对子集进行排序并应用限制。这导致 order by 的工作集更小。如果您对此有很大的问题,那么这里可能还有其他问题。 Where 子句返回多少条记录? 现在我们等待比我们更了解数据库的人来解释解释语句的真正含义以及 MySQL 以这种方式生成它们的原因。在我看来,第一个查询首先排序然后过滤,但为什么... 嵌套选择的性能不佳可能是MySql特有的问题:***.com/questions/13201049/…(另请阅读问题下方的cmets) 【参考方案1】:

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

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

results = results.OrderBy(x=> x.StartTime).Take(limit);

【讨论】:

我敢打赌这不会发生在 SQL Server 中;) 我想知道他们的执行计划构建例程有何不同。我要去尝试和复制。 我对此也很好奇,但懒得自己做研究:)【参考方案2】:

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

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

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

【讨论】:

边想:我真的希望要求记录所有搜索的人实际上查看该数据。目前,它的好处是检测黑客脚本和垃圾邮件机器人,因为它们会搜索无意义的查询。 第一个查询中根本没有 where 子句。我不是 MySQL 专家,但我希望任何体面的数据库引擎都能生成一个查询计划,在任何一种情况下都首先执行 where 子句。 @JamesGaunt - 哎呀,你是对的。我在测试时遇到了复制和粘贴错误。证明无效。 @Bobson 您应该更新您的答案并告诉人们您使用 MS SQL 服务器进行了测试。很混乱! @SimonEdström - 好点。当我得到我的测试结果时,我有那个,但我把它编辑掉了。【参考方案3】:

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

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

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

【讨论】:

看看执行计划。不同语句之间存在巨大的性能差异。 我没看到。两个查询计划都在第一时间实现 WHERE。如果您看到的时间非常不同,则表明正在发生其他事情。不管是这个还是这个都是 MySQL 的“功能”,但 MySQL 是一个广泛使用和受人尊敬的数据库引擎,我不敢相信它会犯这样一个根本错误。您是否使用相同的参数调用两个测试? 啊!只需阅读有问题的@Slauma 的评论!哇。如果是这种情况,请在他们解决之前停止使用 MySQL。子查询不是临时表! 是的,这听起来像是另一种选择【参考方案4】:

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

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

【讨论】:

当 EF 需要执行查询时,它会将表达式树编译为 db 命令。这对于复杂的查询来说是非常昂贵的。如果将变量更改为常量,EF 将无法重用缓存的编译内容,需要重新编译。适用于性能极为不利的复杂查询。【参考方案5】:

我偶然发现了这篇文章,因为我遇到了同样的问题。我已经花了好几天的时间来追踪它,它只是 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

【讨论】:

【参考方案6】:

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

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

【讨论】:

以上是关于为啥实体框架会生成嵌套 SQL 查询?的主要内容,如果未能解决你的问题,请参考以下文章

为啥使用参数化查询或实体框架会阻止 sql 注入?

如何为实体框架 Sql 提供程序编写测试并访问生成的 Sql 命令

如何获取实体框架生成的sql [重复]

实体框架使用 PatIndex 生成的查询不起作用

实体框架 .Any 不生成预期的 SQL WHERE 子句

优化实体框架生成的 SQL Server 执行计划