我正在尝试在我自己的时间制作一个stackoverflow克隆来学习EF6和MVC5,我目前正在使用OWin进行身份验证。
当我有50-60个问题时,一切正常,我使用了Red Gate data generator并试图通过成千上万的子表行来增加到100万个问题而没有任何关系只是为了“压力”ORM a位。以下是linq的样子
var query = ctx.Questions
.AsNoTracking() //read-only performance boost.. http://visualstudiomagazine.com/articles/2010/06/24/five-tips-linq-to-sql.aspx
.Include("Attachments")
.Include("Location")
.Include("CreatedBy") //IdentityUser
.Include("Tags")
.Include("Upvotes")
.Include("Upvotes.CreatedBy")
.Include("Downvotes")
.Include("Downvotes.CreatedBy")
.AsQueryable();
if (string.IsNullOrEmpty(sort)) //default
{
query = query.OrderByDescending(x => x.CreatedDate);
}
else
{
sort = sort.ToLower();
if (sort == "latest")
{
query = query.OrderByDescending(x => x.CreatedDate);
}
else if (sort == "popular")
{
//most viewed
query = query.OrderByDescending(x => x.ViewCount);
}
}
var complaints = query.Skip(skipCount)
.Take(pageSize)
.ToList(); //makes an evaluation..
毋庸置疑,我正在获得SQL超时,并在安装Miniprofiler之后,查看生成的sql语句,这是一个可怕的几百行。
我知道我加入/包含太多表格,但在现实生活中有多少项目,我们只需加入1或2个表格?可能有些情况我们必须用数百万行来做这么多连接,是唯一的存储过程吗?
如果是这样的话,EF本身是否只适合小规模项目?
答案 0 :(得分:16)
您遇到的问题很可能是Cartesian product。
仅基于一些样本数据:
var query = ctx.Questions // 50
.Include("Attachments") // 20
.Include("Location") // 10
.Include("CreatedBy") // 5
.Include("Tags") // 5
.Include("Upvotes") // 5
.Include("Upvotes.CreatedBy") // 5
.Include("Downvotes") // 5
.Include("Downvotes.CreatedBy") // 5
// Where Blah
// Order By Blah
这将返回
以上的行数50 x 20 x 10 x 5 x 5 x 5 x 5 x 5 x 5 = 156,250,000
说真的......这是要返回的INSANE行数。
如果您遇到此问题,确实有两种选择:
第一种:简单的方法,依靠Entity-Framework在进入上下文时自动连接模型。然后,使用实体AsNoTracking()
并处理上下文。
// Continuing with the query above:
var questions = query.Select(q => q);
var attachments = query.Select(q => q.Attachments);
var locations = query.Select(q => q.Locations);
这将为每个表发出一个请求,但不是156百万行,而是只下载110行。但很酷的部分是它们都连接在EF Context Cache内存中,所以现在questions
变量已完全填充。
第二:Create a stored procedure that returns multiple tables and have EF materialize the classes。
答案 1 :(得分:10)
我没有看到您的LINQ查询有任何明显错误(.AsQueryable()
不应该是强制性的,但如果删除它则不会改变任何内容)。当然,不包含不必要的导航属性(每个都添加一个SQL JOIN
),但如果一切都是必需的,那么 就可以了。
现在,当C#代码看起来没问题时,是时候看看生成的SQL代码了。正如您所做的那样,第一步是检索执行的SQL查询。有.Net ways of doing it,对于SQL Server,我个人总是开始SQL Server profiling session。
获得SQL查询后,尝试直接对数据库执行,不要忘记包含actual execution plan。这将在大多数情况下准确显示查询的哪个部分。它甚至会指示您是否有明显丢失的索引。
现在的问题是,如果您添加所有这些索引,您的SQL Server会告诉您它们丢失了吗?不必要。请参阅示例Don't just blindly create those missing indexes。您必须选择应添加哪些索引,而不应添加。
由于代码优先方法为您创建了索引,我假设它们只是主键和外键的索引。这是一个好的开始,但这还不够。我不知道你的表中的行数,但是一个明显的索引只有你可以添加(没有代码生成工具可以做到这一点,因为它与你的业务查询有关),是例如,CreatedDate
列上的索引,因为您按此值排序商品。如果不这样做,SQL Server将不得不在1M行上执行表扫描,这在性能方面当然是灾难性的。
所以:
Include
答案 2 :(得分:5)
正如您所知,Include方法会生成可怕的SQL。
免责声明:我是该项目的所有者Entity Framework Plus (EF+)
EF + Query IncludeOptimized方法允许优化生成的SQL,就像EF Core一样。
生成多个SQL(每个包含一个SQL),而不是生成一个可怕的SQL。此功能也作为奖励,它允许过滤相关实体。
var query = ctx.Questions
.AsNoTracking()
.IncludeOptimized(x => x.Attachments)
.IncludeOptimized(x => x.Location)
.IncludeOptimized(x => x.CreatedBy) //IdentityUser
.IncludeOptimized(x => x.Tags)
.IncludeOptimized(x => x.Upvotes)
.IncludeOptimized(x => x.Upvotes.Select(y => y.CreatedBy))
.IncludeOptimized(x => x.Downvotes)
.IncludeOptimized(x => x.Downvotes.Select(y => y.CreatedBy))
.AsQueryable();
答案 3 :(得分:1)
查看Microsoft的this document部分8.2.2:
8.2.2多个包含的性能问题
当我们听到涉及服务器响应时间的性能问题时 问题,问题的根源是多次查询 包含声明。虽然在查询中包含相关实体 功能强大,了解其下发生的事情非常重要 覆盖。
使用多个Include进行查询需要相对较长的时间 在其中的语句通过我们的内部计划编译器来生成 商店命令。大部分时间都花在了尝试上 优化生成的查询。生成的store命令将包含 每个Include的外部联接或联盟,具体取决于您的映射。 像这样的查询将带来你的大型连接图 单个有效负载中的数据库,可以消除任何带宽 问题,特别是当有效载荷中存在大量冗余时 (即包含多个级别的Include遍历关联 一对多方向。
您可以检查查询过多返回的情况 通过使用访问查询的基础TSQL来获取大型有效负载 ToTraceString并在SQL Server Management中执行store命令 Studio可以查看有效负载大小。在这种情况下,您可以尝试减少 查询中包含语句的数量只是为了引入 你需要的数据。或者您可以将查询分解为更小的查询 子查询序列,例如:
在断开查询之前:
using (NorthwindEntities context = new NorthwindEntities()) { var customers = from c in context.Customers.Include(c => c.Orders) where c.LastName.StartsWith(lastNameParameter) select c; foreach (Customer customer in customers) { ... } }
打破查询后
using (NorthwindEntities context = new NorthwindEntities()) { var orders = from o in context.Orders where o.Customer.LastName.StartsWith(lastNameParameter) select o; orders.Load(); var customers = from c in context.Customers where c.LastName.StartsWith(lastNameParameter) select c; foreach (Customer customer in customers) { ... } }
这仅适用于跟踪查询,因为我们正在使用 上下文必须执行身份解析和关联的能力 自动修复。
与延迟加载一样,权衡将是更小的查询 有效载荷。您还可以使用各个属性的投影 明确地只选择每个实体所需的数据,但是你 在这种情况下不会加载实体,并且不会进行更新 支撑。
答案 4 :(得分:1)
我不同意Ken2k的回答,并且感到惊讶的是,它获得了与之相当多的支持。
从编译的角度来看,代码可能很好,但是如果您关心查询的性能,那么包含这么多的包含绝对不是可以的。 See 8.2.2 of MSFT's EF6 Performance Whitepaper:
当我们听到涉及服务器响应时间问题的性能问题时,问题的根源是经常使用多个包含语句的查询。
看看EF急切地在一个查询中加载许多导航属性(通过大量的.include()语句)所生成的TSQL,这将很明显说明为什么这样做不好。在一个查询中,最终将产生太多EF生成的联接。
中断查询,以使每个表提取中最多包含2个.include()语句。您可以为每个数据集单独执行.Load(),但很可能不需要那么远,YMMV。
var query = ctx.Questions.Where(...);
// Loads Questions, Attachments, Location tables
query.Include(q => q.Attachments)
.Include(q => q.Location)
.Load();
// Loads IdentityUsers Table
query.Select(q => q.CreatedBy).Load();
// Loads Tags
query.Select(q => q.Tags).Load();
// Loads Upvotes and Downvotes
query.Include(q => q.Upvotes)
.Include(q => q.Downvotes)
.Load();
// Assuming Upvotes.CreatedBy and Downvotes.CreatedBy are also an IdentityUser,
// then you don't need to do anything further as the IdentityUser table is loaded
// from query.Select(q => q.CreatedBy).Load(); and EF will make this association for you
Erik提到您可以使用.AsNoTracking(),但我不确定他建议在什么时候使用它,但是如果您需要使用带有填充的导航属性的最终实体集(例如{{1 }}),您不能使用.AsNoTracking(),这会使EF缓存中的实体之间的关联无效(再次,来自MSFT文档的8.2.2):
此[分解EF查询]仅适用于跟踪查询,因为我们正在利用上下文必须自动执行身份解析和关联修正的功能。
为提高性能,如果您的查询是只读的,即您不更新值,则可以在query
上设置以下属性(假设您急于加载所有必需的数据):< / p>
DbContext
最后,您的DbContext应该具有每个请求的生存期/范围。
肯(Ken)的观点是,如果您的数据库体系结构混乱不堪,运行探查器/查看执行计划可以帮助您调整索引/识别其他问题,但是在考虑打开探查器之前,您的查询就受到了限制{{1 }} Configuration.LazyLoadingEnabled = false;
Configuration.AutoDetectChangesEnabled = false;
Configuration.ProxyCreationEnabled = false;
,您应该会发现仅此一项就可以大大提高速度。