Обучение
Модуль
Разработка стратегии секционирования данных - 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]
В этом примере, так как и
Обратите внимание, что декартовый взрыв не происходит, когда два соединения не на одном уровне.
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]
При исследовании прогнозируемых столбцов, каждая строка, возвращаемая этим запросом, содержит свойства из обоих таблиц Blogs
и Posts
. Это означает, что свойства блога дублируются для каждой публикации в блоге. Хотя это обычно нормально и не вызывает проблем, если 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]
Предупреждение
При использовании разделенных запросов с Skip/Take на версиях 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
Разработка стратегии секционирования данных