【发布时间】:2020-01-23 14:10:24
【问题描述】:
我在 C# 应用程序中使用实体框架,并且正在使用延迟加载。我们注意到一个查询对我们的 CPU 有非常大的影响,它只是计算一个总和。在调试实体框架生成的查询时,它会创建一个不高效的INNER JOIN (SELECT ...。当我手动将查询更改为正确的 JOIN 时,查询时间从 1.3 秒变为 0.03 秒。
让我用我的代码的简化版本来说明它。
public decimal GetPortfolioValue(Guid portfolioId)
{
var value = DbContext.Portfolios
.Where( x => x.Id.Equals(portfolioId) )
.SelectMany( p => p.Items
.Where( i => i.Status == ItemStatusConstants.Subscribed
&& _activeStatuses.Contains( i.Category.Status ) )
)
.Select( i => i.Amount )
.DefaultIfEmpty(0)
.Sum();
return value;
}
这会生成一个查询,该查询选择总和,但对连接在一起的两个表的 SELECT 执行内部连接。我为生成的查询创建了一个 pastebin here,以免污染这个问题,但缩短的版本是:
SELECT ...
FROM `portfolios` AS `Extent1`
INNER JOIN (SELECT
`Extent2`.*,
`Extent3`.*
FROM `items` AS `Extent2`
INNER JOIN `categories` AS `Extent3` ON `Extent3`.`id` =
`Extent2`.`category_id`) AS `Join1`
ON `Extent1`.`id` = `Join1`.`portfolio_id`
AND ((`Join1`.`status` = @gp1)
AND (`Join1`.`STATUS1` IN (@gp2, @gp3, @gp4, @gp5, @gp6)))
WHERE ...
我希望它生成的查询(需要 0.03 秒而不是 1.3 秒)类似于
SELECT ...
FROM `portfolios` AS `Extent1`
INNER JOIN `items` AS `Extent2` ON `Extent2`.`portfolio_id` = `Extent1`.`id`
INNER JOIN `categories` AS `Extent3` ON `Extent3`.`id` = `Extent2`.`category_id`
AND ((`Extent2`.`status` = @gp1)
AND (`Extent3`.`status` IN (@gp2, @gp3, @gp4, @gp5, @gp6)))
WHERE ...
我怀疑这是由于.SelectMany 造成的,但我不知道应该如何重写 LINQ 查询以提高效率。至于实体,链接属性是虚拟的,并且配置了外键:
public class Portfolio
{
public Guid Id { get; set; }
public virtual ICollection<Item> Items { get; set; }
}
public class Item
{
public Guid Id { get; set; }
public Guid PortfolioId { get; set; }
public Guid CategoryId { get; set; }
public decimal Amount { get; set; }
public string Status { get; set; }
public virtual Portfolio Portfolio { get; set; }
public virtual Category Category { get; set; }
}
public class Category
{
public Guid Id { get; set; }
public string Status { get; set; }
public virtual ICollection<Item> Items { get; set; }
}
任何帮助将不胜感激!
【问题讨论】:
-
这些只是 JOIN。 服务器 应该能够生成不加载这些字段的执行计划。这两个查询的执行计划是什么?您使用的是哪个 MySQL 版本?查询性能由执行计划控制,而执行计划又取决于查询和数据统计信息。而 MySQL 5.7.x 版本即使在次要版本之间也存在显着差异
-
SQL 查询由数据库提供程序生成,而不是 EF。您使用了哪个提供商?柚?还有什么?还是连接器/网络?如果您使用的是 Connector/NET,您是否尝试过使用其他东西,例如 Pomelo?
-
也许您可以尝试稍微更改一下您的查询。假设portfolioId 是唯一的,您可以像这样更改您的查询: var value = DbContext.Portfolios .First( x => x.Id.Equals(portfolioId) ).Items...
-
it creates a INNER JOIN (SELECT ... which is not performant.为什么?除非查询优化器未能简化查询,否则这相当于只是将所有表连接在一起。如果服务器在这方面发生故障,那是服务器错误。查询中的 真的很奇怪 构造是(SELECT 1 AS 'X') AS 'SingleRowTable1' LEFT OUTER JOIN ...ON 1=1。这可能是一些在以后的版本中适得其反的未记录技巧吗? -
再次,您使用的是哪个提供商?如果
Item不是实体,则提供商本身应该抱怨。它不应该发出基于反射的 SQL 查询。它太聪明了
标签: c# mysql performance entity-framework linq