Что делать, если на продакшене начал тормозить запрос?

Ответ

Проведу анализ и устранение проблемы по шагам:

  1. Сначала проверю медленный запрос через Application Insights или аналогичный инструмент мониторинга, чтобы понять точное время выполнения.

  2. Проанализирую план выполнения запроса в SQL Server Profiler или через EXPLAIN в других СУБД. Ищу:

    • Отсутствующие индексы
    • Табличные сканы вместо поиска по индексу
    • Сложные JOIN-ы
    • Проблемы с параметризацией
  3. Оптимизирую запрос:

// Было
var orders = db.Orders.Where(o => o.Date.Year == 2023).ToList();

// Стало - с явным указанием дат и индекса
var startDate = new DateTime(2023, 1, 1);
var endDate = new DateTime(2023, 12, 31);
var orders = db.Orders.Where(o => o.Date >= startDate && o.Date <= endDate).ToList();
  1. Проверю наличие блокировок в БД (deadlocks, long-running transactions).

  2. Рассмотрю кеширование результатов для часто используемых запросов.

  3. Если проблема не решается - добавлю временное логирование и алертинг, чтобы собрать больше данных.

Ответ 18+ 🔞

Ну ты представляешь, сидишь такой, смотришь в монитор, а тут запрос тормозит как черепаха в патруле. Ебать колотить, ну что за дела! Ладно, давай по косточкам разберём эту дичь.

Первым делом, конечно, лезем в Application Insights или что там у тебя по мониторингу. Надо посмотреть, сколько конкретно эта поебота выполняется. Может, тебе кажется, что вечность, а там 200 миллисекунд — расслабься тогда и не парься. Но если реально овердохуища секунд, тогда пиздец, надо ковырять.

Дальше самое интересное — план выполнения запроса. Берёшь SQL Server Profiler, или там EXPLAIN в постгресе, и начинаешь изучать эту картину маслом. Ищешь, блядь, три вещи: где нет индексов (и от этого идёт табличное сканирование, то есть БД читает всю таблицу как дура), где JOIN-ы такие кривые, что серверу хочется плакать, и где параметризация сломалась и для каждого запроса новый план генерируется — вот это вообще пиздец.

Сам запрос часто бывает написан криво. Смотри, классика жанра:

// Типичный говнокод новичка
var orders = db.Orders.Where(o => o.Date.Year == 2023).ToList();

Выглядит вроде норм, да? А на деле — пизда рулю. Потому что функция Year() убивает любой индекс по дате. База не может использовать индекс, она вынуждена каждую запись вычислять год и сравнивать. В итоге вместо быстрого поиска — полный перебор. Ужас!

Правильно делать вот так:

// А вот так уже человек думает головой
var startDate = new DateTime(2023, 1, 1);
var endDate = new DateTime(2023, 12, 31, 23, 59, 59);
var orders = db.Orders.Where(o => o.Date >= startDate && o.Date <= endDate).ToList();

Видишь разницу? Теперь запрос может использовать индекс по полю Date, если он, конечно, есть. А если нет — ну, сам дурак, иди создавай.

Дальше смотришь, нет ли в базе блокировок. Может, какой-то долбанный deadlock сидит, или транзакция на полчаса открыта, и все остальные ждут её, как дураки. Это отдельная песня, её тоже надо ловить.

Если всё вышеперечисленное не помогло, думаешь про кеширование. Может, этот запрос вообще не надо каждый раз гонять в базу? Результаты не часто меняются? Закешировал на пять минут — и все счастливы, нагрузка упала в разы.

Ну и последний аргумент — если вообще нихуя не понятно, в чём дело, ставишь подробное логирование вокруг этого места и алертинг. Пусть прилетает уведомление, когда запрос начинает ебашить дольше трёх секунд, и в лог пишутся все параметры. Потом по этим логам уже как сыр в масле катаешься, находишь закономерности и добиваешь проблему.

В общем, алгоритм простой: замеряем, смотрим план, правим запрос, проверяем блокировки, думаем про кеш, если не помогает — логируем и снова смотрим. Рано или поздно вылезет какая-нибудь хуйня, которая всё тормозит. Главное — не сдаваться, чувак.