Ответ
Оптимизация запросов — ключевой навык для работы с высоконагруженными приложениями. Вот основные практики, сфокусированные на SQL-серверах и ORM вроде Entity Framework Core.
1. Индексы — основа скорости
Добавляйте индексы на столбцы, используемые в WHERE, JOIN, ORDER BY, GROUP BY. Но не индексируйте всё подряд — каждый индекс замедляет операции INSERT, UPDATE, DELETE.
-- Создание индекса в SQL
CREATE INDEX IX_Users_Email ON Users(Email);
CREATE UNIQUE INDEX IX_Users_Username ON Users(Username);
// Определение индексов в EF Core (Fluent API)
modelBuilder.Entity<User>()
.HasIndex(u => u.Email)
.IsUnique();
// Составной индекс
modelBuilder.Entity<Order>()
.HasIndex(o => new { o.CustomerId, o.CreatedDate });
2. Выбирайте только нужные данные
Избегайте SELECT *. Запрашивайте только необходимые столбцы.
// ПЛОХО: Выбирает все поля
var allData = context.Users.ToList();
// ХОРОШО: Проекция (Select)
var userNames = context.Users
.Where(u => u.IsActive)
.Select(u => new { u.Id, u.FullName, u.Email })
.ToList();
3. Используйте пагинацию для больших наборов данных
Никогда не загружайте все записи таблицы.
// Пагинация с помощью Skip и Take
int pageNumber = 2, pageSize = 20;
var pagedUsers = context.Users
.OrderBy(u => u.Id)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToList();
4. Контролируйте загрузку связанных данных (N+1 проблема)
- Проблема N+1: Для каждой основной сущности выполняется отдельный запрос для загрузки связанных данных.
- Решение: Используйте жадную загрузку (
Include,ThenInclude) или явную загрузку (Load).
// Жадная загрузка (Eager Loading) - данные загружаются одним запросом
var ordersWithItems = context.Orders
.Include(o => o.Items) // JOIN с таблицей OrderItems
.ThenInclude(i => i.Product) // JOIN с таблицей Products
.Where(o => o.Date > DateTime.UtcNow.AddDays(-30))
.ToList();
5. Анализируйте план выполнения запроса
Используйте EXPLAIN (PostgreSQL, MySQL) или "Display Estimated Execution Plan" (SQL Server), чтобы понять, как СУБД выполняет ваш запрос, и найти "узкие" места (отсутствие индексов, сканирование таблиц).
6. Кэшируйте статические или редко меняющиеся данные
Используйте Redis, MemoryCache или кэш второго уровня в EF Core для данных, которые часто читаются, но редко обновляются.
// Пример с IMemoryCache в ASP.NET Core
public async Task<Product> GetProductAsync(int id)
{
var cacheKey = $"product_{id}";
if (!_cache.TryGetValue(cacheKey, out Product product))
{
product = await _context.Products.FindAsync(id);
_cache.Set(cacheKey, product, TimeSpan.FromMinutes(10));
}
return product;
}
7. Объединяйте операции (Batching)
Для массовых вставок или обновлений используйте специальные методы (например, AddRange, BulkInsert из библиотек вроде EFCore.BulkExtensions), чтобы избежать тысяч отдельных запросов.
Главное правило: Всегда измеряйте производительность до и после оптимизации с помощью профилировщиков (SQL Server Profiler, Application Insights, MiniProfiler).