繁体   English   中英

Entity Framework Core 3.0 查询导致“SqlException: 'Execution Timeout Expired'”和 tempdb 变满。 适用于 EF Core 2.2.6

[英]Entity Framework Core 3.0 query causes “SqlException: 'Execution Timeout Expired'” and tempdb become full. Works with EF Core 2.2.6

我在 Microsoft Entity Framework Core 3.0 中运行一个相当简单的查询,如下所示:

var dbProfile = db.Profiles.Where(x => x.SiteId == Int32.Parse(id))
    .Include(x => x.Interests)
    .Include(x => x.Pets)
    .Include(x => x.Networks)
    .Include(x => x.PersonalityTraits)
    .SingleOrDefault();

它在 EF Core 2.2.6 上运行良好,但在升级到 EF Core 3.0 时,此查询会立即针对 721 个配置文件运行,但对于至少一个配置文件,查询会超时:

Microsoft.Data.SqlClient.SqlException:'执行超时已过期。
在操作完成之前超时时间已过或服务器没有响应。

然后我记录了发送到数据库服务器的实际查询:

https://stackoverflow.com/a/58348159/3850405

SELECT [t].[Id], [t].[Age], [t].[City], [t].[Country], [t].[County], [t].[DeactivatedAccount], [t].[Gender], [t].[HasPictures], [t].[LastLogin], [t].[MemberSince], [t].[PresentationUpdated], [t].[ProfileName], [t].[ProfilePictureUrl], [t].[ProfileText], [t].[SiteId], [t].[VisitorsCount], [i].[Id], [i].[Name], [i].[ProfileId], [p0].[Id], [p0].[Description], [p0].[Name], [p0].[ProfileId], [n].[Id], [n].[Name], [n].[NetworkId], [n].[ProfileId], [p1].[Id], [p1].[Name], [p1].[ProfileId]
FROM (
    SELECT TOP(2) [p].[Id], [p].[Age], [p].[City], [p].[Country], [p].[County], [p].[DeactivatedAccount], [p].[Gender], [p].[HasPictures], [p].[LastLogin], [p].[MemberSince], [p].[PresentationUpdated], [p].[ProfileName], [p].[ProfilePictureUrl], [p].[ProfileText], [p].[SiteId], [p].[VisitorsCount]
    FROM [Profiles] AS [p]
    WHERE ([p].[SiteId] = '123') AND '123' IS NOT NULL
) AS [t]
LEFT JOIN [Interests] AS [i] ON [t].[Id] = [i].[ProfileId]
LEFT JOIN [Pets] AS [p0] ON [t].[Id] = [p0].[ProfileId]
LEFT JOIN [Networks] AS [n] ON [t].[Id] = [n].[ProfileId]
LEFT JOIN [PersonalityTraits] AS [p1] ON [t].[Id] = [p1].[ProfileId]
ORDER BY [t].[Id], [i].[Id], [p0].[Id], [n].[Id], [p1].[Id]

然后我尝试在 SSMS 中运行实际的 SQL 并最终出现以下错误:

消息 1105,第 17 层,State 2,第 1 行
无法为数据库“tempdb”中的 object“dbo.SORT 临时运行存储:140737692565504”分配空间,因为“PRIMARY”文件组已满。 通过删除不需要的文件、删除文件组中的对象、向文件组添加其他文件或为文件组中的现有文件设置自动增长来创建磁盘空间。

我的tempdb现在已经完全填满了数据库磁盘。 我已经尝试了 10 个其他 ID,并且相同的查询立即运行。

我尝试使用命令DBCC SHRINKDATABASE(tempdb, 10); 它工作得很好。 但是,当我再次尝试运行查询时,同样的事情发生了。 如果我跳过包括表格,一切都很好。 这里可能有什么问题,我该如何解决? 这是 EF Core 3.0 中的已知错误吗? 查看 EF Core 2.2.6 中的查询,它对所有表执行像这样的单独选择:

SELECT [x.Interests].[Id], [x.Interests].[Name], [x.Interests].[ProfileId]
FROM [Interests] AS [x.Interests]
INNER JOIN (
    SELECT TOP(1) [x0].[Id]
    FROM [Profiles] AS [x0]
    WHERE [x0].[SiteId] = '123'
    ORDER BY [x0].[Id]
) AS [t] ON [x.Interests].[ProfileId] = [t].[Id]
ORDER BY [t].[Id]

这是 EF Core 3 中记录的重大更改: 相关实体的急切加载现在发生在单个查询中

新行为类似于 EF6 中的查询生成,其中多个包含可以创建非常大且昂贵的查询。 这些查询也可能由于超时、查询计划生成成本或查询执行资源耗尽而失败。

因此,就像在 EF6 中一样,您需要避免包含多个不相关的实体包含路径,因为这些路径会创建非常昂贵的查询。

相反,您可以使用延迟加载,或在单独的查询中显式加载实体图的一部分,并让更改跟踪器修复导航属性。

EF 5 添加了一个选项来关闭称为拆分查询的大查询生成。

除了@DavidBrowne-Microsoft 答案。

假设我们在 model 中有以下实体和导航

Customer
Customer.Address (reference nav)
Customer.Orders (collection nav)
Order.OrderDetails (collection nav)
Order.OrderDiscount (reference nav)

您只需要为集合导航重写,参考导航可以是同一查询的一部分。

var baseQuery = db.Customers.Include(c => c.Address).Where(c => c.CustomerName == "John");
var result = baseQuery.ToList(); // Or async method, If doing FirstOrDefault, add Take(1) to base query
baseQuery.Include(c => c.Orders).ThenInclude(o => o.OrderDiscount).SelectMany(c => c.Orders).Load();
baseQuery.SelectMany(c => c.Orders).SelectMany(o => o.OrderDetails).Load();

这将生成 3 个对服务器的查询。 它会比 EF Core 2.2 生成的 SQL 稍微优化一些。 StateManager 将修复导航。 它还避免了将来自服务器的任何记录复制到客户端。

生成的 SQL:

// Customer Include Address
SELECT [c].[Id], [c].[CustomerName], [a].[Id], [a].[City], [a].[CustomerId]
FROM [Customers] AS [c]
LEFT JOIN [Address] AS [a] ON [c].[Id] = [a].[CustomerId]
WHERE ([c].[CustomerName] = N'John') AND [c].[CustomerName] IS NOT NULL

// Order Include Order discount
SELECT [o].[Id], [o].[CustomerId], [o].[OrderDate], [o0].[Id], [o0].[Discount], [o0].[OrderId]
FROM [Customers] AS [c]
INNER JOIN [Order] AS [o] ON [c].[Id] = [o].[CustomerId]
LEFT JOIN [OrderDiscount] AS [o0] ON [o].[Id] = [o0].[OrderId]
WHERE ([c].[CustomerName] = N'John') AND [c].[CustomerName] IS NOT NULL

// OrderDetails
SELECT [o0].[Id], [o0].[OrderId], [o0].[ProductName]
FROM [Customers] AS [c]
INNER JOIN [Order] AS [o] ON [c].[Id] = [o].[CustomerId]
INNER JOIN [OrderDetail] AS [o0] ON [o].[Id] = [o0].[OrderId]
WHERE ([c].[CustomerName] = N'John') AND [c].[CustomerName] IS NOT NULL

编辑:如果您没有在 SelectMany 中使用的 CLR 导航,那么您可以使用EF.Property来引用集合导航。

来源: https://github.com/aspnet/EntityFrameworkCore/issues/18022#issuecomment-537219137

你需要像这样更改你的代码,然后它将生成更多查询并避免超时

var dbProfile = db.Profiles.SingleOrDefault(x => x.SiteId == Int32.Parse(id));
dbProfile.Include(x => x.Interests).Load();
dbProfile.Include(x => x.Pets).Load();
dbProfile.Include(x => x.Networks).Load();
dbProfile.Include(x => x.PersonalityTraits).Load();

请记住,查询应在跟踪模式下执行。 如果它不加载孩子,你可以像这样添加 asTracking:

db.Profiles.AsTracking().Where(........

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM