Обучение
Модуль
Разработка стратегии секционирования данных - Training
Разработка стратегии секционирования данных
Этот браузер больше не поддерживается.
Выполните обновление до Microsoft Edge, чтобы воспользоваться новейшими функциями, обновлениями для системы безопасности и технической поддержкой.
При работе с реляционными базами данных EF загружает связанные сущности, введя JOIN в один запрос. Хотя joIN довольно стандартны при использовании SQL, они могут создавать значительные проблемы с производительностью при неправильном использовании. На этой странице описываются эти проблемы с производительностью и показан альтернативный способ загрузки связанных сущностей, которые работают вокруг них.
Давайте рассмотрим следующий запрос LINQ и его эквивалент SQL:
var blogs = await ctx.Blogs
.Include(b => b.Posts)
.Include(b => b.Contributors)
.ToListAsync();
SELECT [b].[Id], [b].[Name], [p].[Id], [p].[BlogId], [p].[Title], [c].[Id], [c].[BlogId], [c].[FirstName], [c].[LastName]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
LEFT JOIN [Contributors] AS [c] ON [b].[Id] = [c].[BlogId]
ORDER BY [b].[Id], [p].[Id]
В этом примере, так как оба и Contributors
являются навигацией Blog
по коллекции , они находятся на одном уровне — реляционные базы данных возвращают перекрестный продукт: каждая строка из Posts
них присоединяется к каждой строке изContributors
.Posts
Это означает, что если в данном блоге есть 10 записей и 10 участников, база данных возвращает 100 строк для этого одного блога. Это явление , иногда называемое декартовским взрывом , может вызвать огромные объемы данных для непреднамеренно передаваться клиенту, особенно так как в запрос добавляются более одноуровневые joIN. Это может быть основной проблемой производительности в приложениях баз данных.
Обратите внимание, что взрыв декарта не происходит, когда два JOIN не на одном уровне:
var blogs = await ctx.Blogs
.Include(b => b.Posts)
.ThenInclude(p => p.Comments)
.ToListAsync();
SELECT [b].[Id], [b].[Name], [t].[Id], [t].[BlogId], [t].[Title], [t].[Id0], [t].[Content], [t].[PostId]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
LEFT JOIN [Comment] AS [c] ON [p].[Id] = [c].[PostId]
ORDER BY [b].[Id], [t].[Id]
В этом запросе Comments
используется навигация Post
по коллекции, в отличие от Contributors
предыдущего запроса, которая была навигацией Blog
по коллекции. В этом случае возвращается одна строка для каждого комментария, который блог имеет (через свои записи), а перекрестный продукт не происходит.
JoIN может создать другой тип проблемы с производительностью. Давайте рассмотрим следующий запрос, который загружает только одну навигацию по коллекции:
var blogs = await ctx.Blogs
.Include(b => b.Posts)
.ToListAsync();
SELECT [b].[Id], [b].[Name], [b].[HugeColumn], [p].[Id], [p].[BlogId], [p].[Title]
FROM [Blogs] AS [b]
LEFT JOIN [Posts] AS [p] ON [b].[Id] = [p].[BlogId]
ORDER BY [b].[Id]
Изучение проецируемых столбцов, каждая строка, возвращаемая этим запросом, содержит свойства как из таблиц, так и Posts
из Blogs
таблиц. Это означает, что свойства блога дублируются для каждой записи, которая имеется в блоге. Хотя это обычно нормально и не вызывает проблем, если Blogs
таблица имеет очень большой столбец (например, двоичные данные или огромный текст), этот столбец будет дублироваться и отправляться клиенту несколько раз. Это может значительно увеличить сетевой трафик и негативно повлиять на производительность приложения.
Если вам не нужен огромный столбец, просто не запрашивать его:
var blogs = await ctx.Blogs
.Select(b => new
{
b.Id,
b.Name,
b.Posts
})
.ToListAsync();
Используя проекцию для явного выбора нужных столбцов, можно опустить большие столбцы и повысить производительность; обратите внимание, что это хорошая идея независимо от дублирования данных, поэтому рекомендуется делать это даже при не загрузке навигации по коллекции. Тем не менее, так как этот блог является анонимным типом, блог не отслеживается EF и изменения в нем не могут быть сохранены как обычно.
Следует отметить, что в отличие от декартового взрыва, дублирование данных, вызванное JOIN, обычно не является значительным, так как дублированный размер данных не является незначительным; Обычно это что-то беспокоиться только о наличии больших столбцов в основной таблице.
Чтобы обойти описанные выше проблемы с производительностью, EF позволяет указать, что заданный запрос LINQ должен быть разделен на несколько запросов SQL. Вместо запросов JOIN разделенные запросы создают дополнительный SQL-запрос для каждой включенной навигации по коллекции:
using (var context = new BloggingContext())
{
var blogs = await context.Blogs
.Include(blog => blog.Posts)
.AsSplitQuery()
.ToListAsync();
}
Будет создан следующий SQL-запрос:
SELECT [b].[BlogId], [b].[OwnerId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
ORDER BY [b].[BlogId]
SELECT [p].[PostId], [p].[AuthorId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title], [b].[BlogId]
FROM [Blogs] AS [b]
INNER JOIN [Posts] AS [p] ON [b].[BlogId] = [p].[BlogId]
ORDER BY [b].[BlogId]
Предупреждение
При использовании разделенных запросов с версиями EF до 10 следует обратить особое внимание на то, что заказ запросов полностью уникален; это не может привести к возврату неправильных данных. Например, если результаты упорядочены только по дате, но может быть несколько результатов с одной и той же датой, то каждый из разделенных запросов может получить разные результаты из базы данных. Упорядочивание по дате и идентификатору (или любому другому уникальному свойству или сочетанию свойств) делает порядок полностью уникальным и помогает избежать этой проблемы. Обратите внимание, что по умолчанию в реляционных базах данных не применяется упорядочение, даже по первичному ключу.
Примечание
Сущности со связями "один к одному" всегда загружаются с помощью запросов JOIN в одном запросе, так как они не влияют на производительность.
Можно также настроить разделение запросов по умолчанию для контекста приложения.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True;ConnectRetryCount=0",
o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
}
Если разделение запросов настроено по умолчанию, можно по-прежнему настроить определенные запросы для выполнения в виде отдельных запросов.
using (var context = new SplitQueriesBloggingContext())
{
var blogs = await context.Blogs
.Include(blog => blog.Posts)
.AsSingleQuery()
.ToListAsync();
}
EF Core по умолчанию использует режим одиночных запросов без всякой конфигурации. Поскольку это может вызвать проблемы с производительностью, EF Core выдает предупреждение при соблюдении следующих условий:
AsSingleQuery
/AsSplitQuery
.Чтобы отключить предупреждение, настройте режим разбиения запроса глобально или на уровне запроса, использовав соответствующее значение.
Хотя разделение запросов позволяет избежать проблем с производительностью, связанных с запросами JOIN и картезианским взрывом, этот способ также имеет некоторые недостатки.
К сожалению, единой стратегии загрузки связанных сущностей, которая подходит для всех сценариев, не существует. Взвешенно оцените преимущества и недостатки использования отдельных и разделенных запросов и выберите оптимальный подход.
Отзыв о .NET
.NET — это проект с открытым исходным кодом. Выберите ссылку, чтобы оставить отзыв:
Обучение
Модуль
Разработка стратегии секционирования данных - Training
Разработка стратегии секционирования данных