| layout | default | |||
|---|---|---|---|---|
| title | 🔍 Как диагностировать и оптимизировать запросы к БД? | |||
| description | ||||
| author | Dvurechensky | |||
| date | 2025-08-28 | |||
| published | true | |||
| tags |
|
- ✨ Оглавление
Даже идеальный код на C# не спасёт, если SQL работает медленно:
- Высокая нагрузка на сервер БД → блокировки, таймауты.
- Плохое использование индексов → full table scan.
- Много лишних запросов → N+1 проблема.
- Потеря производительности приложения → UI тормозит, Web API задержки.
- EF Core:
var sql = context.Users.Where(u => u.IsActive).ToQueryString();
Console.WriteLine(sql);- SQL Server Profiler / Extended Events / Query Store – отслеживание фактических SQL-запросов, времени выполнения, планов.
- MiniProfiler / EFCore Diagnostics – лёгкие инструменты для приложений.
- Каверзный момент: EF может генерировать сложные JOIN/подзапросы без очевидной причины → всегда проверяй сгенерированный SQL.
-
В SQL Server:
SET STATISTICS PROFILE ON;или просмотрExecution Plan. -
Что искать:
- Full Table Scan vs Index Seek.
- Nested Loops vs Hash Join.
- Сортировки и вычисления на сервере.
-
Каверзный момент: EF иногда приводит к созданию неиспользуемых индексов или сложных подзапросов.
- Правильные индексы ускоряют SELECT, но замедляют INSERT/UPDATE/DELETE.
- Используй
INCLUDEколонки для covering index. - Каверзный момент: лишние индексы → нагрузка на запись, мало индексов → медленный SELECT.
- В .NET:
Stopwatchдля измерения времени запроса.
var sw = Stopwatch.StartNew();
var data = await context.Users.AsNoTracking().ToListAsync();
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);- Каверзный момент: измерение без AsNoTracking может показывать дополнительное время на трекинг объектов.
- Измеряй, сколько реально строк возвращается.
- Используй
Count(),Take(10)для проверки, чтобы не тянуть миллионы строк в память. - Каверзный момент:
ToList()без фильтрации → memory spike.
- Никогда не фильтруй после
ToList()в памяти.
// Плохо
var data = context.Users.ToList().Where(u => u.IsActive);
// Хорошо
var data = context.Users.Where(u => u.IsActive).ToList();- Выбираем только нужные колонки.
var data = context.Users
.Where(u => u.IsActive)
.Select(u => new { u.Id, u.Name })
.ToList();- Каверзный момент: иногда Include + Select работает медленнее, чем несколько отдельных запросов.
- LINQ запросы выполняются только при materialization (
ToList,First,Count). - Используй async для Web и UI потоков.
var users = await context.Users.Where(u => u.IsActive).ToListAsync();- Для массовых операций использовать BulkInsert/BulkUpdate или Dapper.
- EF Core по умолчанию делает отдельные INSERT/UPDATE → медленно.
- Каверзный момент: при больших вставках EF может генерировать миллионы команд → OutOfMemory.
- Проверяй, что JOIN-ы используют ключи.
- Сортировка и фильтры по индексированным колонкам → быстрее.
- Каверзный момент: фильтры на вычисляемых колонках могут игнорировать индексы.
- Используй Skip/Take с OrderBy.
var page = context.Users
.OrderBy(u => u.Id)
.Skip((page-1)*pageSize)
.Take(pageSize)
.ToList();- Без
OrderBy→ непредсказуемый результат.
- Кэш на уровне приложения: MemoryCache, Redis.
- Кэш на уровне EF: ChangeTracker + AsNoTracking.
- Каверзный момент: кэш устарел → возвращает старые данные.
- Используй один DbContext на единицу работы.
- Не держи DbContext долго → рост памяти и трекинга.
- SQL Profiler / Query Store – для SQL Server.
- EXPLAIN / EXPLAIN ANALYZE – для PostgreSQL.
- MiniProfiler / Glimpse – в приложении.
✨Dvurechensky✨