2012-11-06 285 views
18

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

我有这样的代码

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

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

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语句?我在Code First中使用MySQL和Entity Framework。

更新

我有相同的结果而不管传递给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语句时,它永远不会超过一秒。也许我还有别的事情在发生;我问:“因为我不是那么到数据块......

更新3:EXPLAIN语句

实体框架生成

'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。我不想完全重写应用程序层。

+3

首先的嵌套查询LINQ To SQL和实体框架是不同的东西。其次为什么你认为这个查询是不好的?你做过绩效分析还是至少要解释? – Stilgar

+1

是的我在纯SQL中手动构建它们。有2800万条记录,有毫秒vs分钟 –

+0

对不起,Linq To SQL打字错误 –

回答

7

这是从表达式树中逻辑构建查询的最简单方法。通常情况下,性能不会成为问题。如果您有性能问题,你可以尝试这样的事情来获得实体回:

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

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

是的,我做到了,它造成巨大的性能影响 –

+2

我敢打赌,这不会发生在SQL Server;)我不知道他们的执行计划建设程序如何不同。我即将尝试再现。 – PeteGO

+2

我也很好奇,但也懒得做研究:) – Stilgar

1

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

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

+0

非常有趣。你的代码在哪里? –

+0

当EF需要执行查询时,它将表达式树编译为db命令。这对于复杂的查询来说非常昂贵。如果您将变量更改为常量,EF将无法重新使用缓存的编译内容,并且需要重新编译。对于一个极端性能杀手的复杂查询。 – MBoros

-2

实际上,由实体框架生成的查询很少丑,比LINQ 2 SQL少,但仍然很丑。

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

3

我最初的印象是,做这种方式实际上是更有效的,虽然在对抗MSSQL服务器测试中,我得到了<1秒响应不管。

有了一个SELECT语句,它排序的所有记录(Order By),然后将它们过滤到你想看到(Where)设定,然后取前5名(Limit 5或者,对我来说,Top 5)。在一张大桌子上,排序需要很长时间。使用嵌套语句时,它首先将记录过滤为子集,然后才对其进行昂贵的排序操作。

编辑:我做了这个测试,但是我意识到我在我的测试中有一个错误,使它失效。测试结果已删除。

+0

侧想:我真的希望那些要求所有搜索的人实际登录*查看*这些数据。现在,所有这一切的好处是检测黑客脚本和垃圾邮件,因为他们搜索无意义的查询。 – Bobson

+1

在第一个查询中根本没有where子句。我不是MySQL专家,但我希望任何体面的数据库引擎都能产生一个查询计划,在这两种情况下都首先执行where子句。 –

+0

@JamesGaunt - 哎呀,你是对的。我在测试时遇到了复制和粘贴错误。证明无效。 – Bobson

2

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

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

所有这些都表示您手动编写的查询与此例中生成的查询EF之间应该没有显着的速度差异。数据库足够聪明,可以生成执行计划,无论是哪种情况,都应首先应用WHERE子句。

+0

看看执行计划。这是不同声明之间巨大的性能差异。 –

+0

我没有看到。这两个查询计划都在第一时间实现WHERE。如果你看到非常不同的时间表示其他事情正在进行。不管是这个还是这个MySQL的'特性',但是MySQL是一个广泛使用和受人尊敬的数据库引擎,我不能相信它会造成这样一个根本性的错误。你是否用相同的参数调用两个测试? –

+0

啊!请阅读@Slauma评论!哇。停止使用MySQL,直到他们排除它,如果是这样的话。子查询不是临时表! –

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` 

和漂亮的执行好。无论如何,外部查询几乎没用。现在,如果我添加一个排序依据

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有拉每个记录,以便执行导致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 
相关问题