Запрос Entity Framework Core 3.0 вызывает исключение SqlException: «Истекло время ожидания выполнения» и база данных tempdb заполняется. Работает с 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]

Затем я попытался запустить фактический SQL в SSMS и получил следующую ошибку:

Msg 1105, уровень 17, состояние 2, строка 1
Не удалось выделить место для объекта «dbo.SORT временное хранилище выполнения: 140737692565504» в базе данных «tempdb», поскольку файловая группа «PRIMARY» заполнена. Создайте дисковое пространство, удалив ненужные файлы, отбросив объекты в файловой группе, добавив дополнительные файлы в файловую группу или включив автоматический рост для существующих файлов в файловой группе.

Мой tempdb теперь полностью заполнил диск с базой данных. Я пробовал 10 других идентификаторов, и тот же запрос выполняется мгновенно.

Я попытался снова сжать базу данных tempdb с помощью команды 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]

comment
Если объем данных огромен, этот запрос должен выполняться без проблем. tempdb вздутие обычно означает, что таблицы некорректно проиндексированы. Есть ли у вас индексы по всем полям внешнего ключа и SiteId? Кроме того, статистика бега может иметь огромное значение.   -  person Gert Arnold    schedule 21.11.2020


Ответы (3)


Это задокументированное критическое изменение в EF Core 3: Теперь нетерпеливая загрузка связанных сущностей происходит в одном запросе

Новое поведение похоже на создание запросов в EF6, где несколько включений могут создавать очень большие и дорогостоящие запросы. Эти запросы также могут завершаться ошибкой из-за тайм-аутов, затрат на создание плана запроса или исчерпания ресурсов для выполнения запроса.

Так же, как и в EF6, вам нужно воздерживаться от включения нескольких несвязанных путей включения сущностей, поскольку они создают очень дорогие запросы.

Вместо этого вы можете использовать отложенную загрузку или явно загружать части графа сущностей в отдельных запросах и позволить отслеживателю изменений исправить свойства навигации.

В EF 5 добавлена ​​возможность отключения генерации одного большого запроса под названием Разделить запросы.

person David Browne - Microsoft    schedule 12.10.2019
comment
Похоже, мы наткнулись на ту же проблему. Я просто хочу подтвердить, та же проблема. После обновления с 2.2 до 3.1 мы также получаем таймауты на запросы, но, похоже, это происходит не каждый раз. 19/20 запросов будут выполняться за 100 миллисекунд, а 1/20 - за 5 минут и никогда не завершится. Вот что в этом такого странного. Я могу понять снижение производительности, но чего не понимаю, так это отсутствия согласованности. - person Mateusz Migała; 24.05.2021
comment
Может быть. Это чаще всего встречается с несколькими включениями, которые создают несколько веток включения. Но вам нужно будет профилировать выполнение запроса на сервере, чтобы быть уверенным. - person David Browne - Microsoft; 24.05.2021

Дополнение к ответу @ DavidBrowne-Microsoft.

Предположим, у нас есть следующие сущности и элементы навигации в модели

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 запроса к серверу. Это был бы немного более оптимизированный SQL, чем то, что генерировал EF Core 2.2. И 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

Изменить: если у вас нет навигации CLR для использования в SelectMany, вы можете использовать EF.Property для ссылки на навигацию по коллекции.

Источник: https://github.com/aspnet/EntityFrameworkCore/issues/18022#issuecomment-537219137

person Ogglas    schedule 14.10.2019

Вам нужно изменить свой код вот так, тогда он будет генерировать больше запросов и избежать тайм-аута.

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(........
person Jafar ashrafi    schedule 28.04.2020