0Pricing
Coding Interview Prep · Урок

Поиск и исправление медленных запросов

Диагностический список для вопроса на собеседовании: «этот запрос работает медленно, исправьте его»

«Поиск и исправление медленных запросов» — бесплатный урок Coding Interview Prep на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Coding Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Coding Interview Prep содержит 4 уроков всего.

Задание «Запрос медленный — исправьте его»

Это итоговое задание на собеседовании: интервьюер показывает вам медленный запрос и план EXPLAIN ANALYZE и просит поставить диагноз. Проверяется не умение запоминать приёмы, а наличие метода.

Сильный ответ строится по контрольному списку, который нужно проговаривать вслух: измерить, прочитать план, найти основную стоимость, сформулировать гипотезу, предложить исправление и проверить результат. Этот урок последовательно формирует такой список.

Сохраняйте системный подход и объясняйте ход рассуждений — именно это даёт оценку уровня старшего разработчика.

Шаг 1. Измерение с помощью EXPLAIN ANALYZE

Никогда не делайте выводы только по SQL. Получите настоящий план с помощью EXPLAIN (ANALYZE, BUFFERS).

ANALYZE показывает фактическое время и количество строк, а BUFFERS — используются ли данные из кэша или читаются с диска. Вместе эти сведения показывают, ограничен ли запрос процессором, операциями ввода-вывода или просто выполняет слишком много работы.

Выполните запрос несколько раз: при первом запуске могут возникнуть потери из-за холодного кэша, и это исказит время выполнения.

EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.created_at >= '2026-01-01';

Шаг 2. Поиск основного узла

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

Вычислите собственное время каждого узла: его общее actual time минус время дочерних узлов, умноженное на значение loops. Узел с наибольшей долей — ваша цель; всё остальное вторично.

На собеседовании скажите: 80 процентов времени выполнения приходится на этот Seq Scan, поэтому я сосредоточусь на нём. Оптимизация чего-либо другого была бы пустой тратой усилий.

Шаг 3. Сравнение оценочных и фактических значений

В основном узле сравните оценочное и фактическое количество строк. Большой разрыв означает, что планировщик действует вслепую и, скорее всего, выбрал неудачный план (неправильный алгоритм соединения или способ доступа).

В примере количество строк недооценено в 1000 раз. Прежде чем что-либо перепроектировать, обновите статистику: одна эта команда часто бесплатно исправляет план.

ANALYZE пересчитывает статистику столбцов, а VACUUM ANALYZE также очищает мёртвые кортежи и обновляет карту видимости.

-- estimate rows=100, actual rows=120000  -> stale stats
ANALYZE orders;
-- or, for bloated tables:
VACUUM ANALYZE orders;

Распространённая причина: функция над индексированным столбцом

Самая частая исправимая ошибка: в WHERE столбец обёрнут функцией или приведением типа, поэтому индекс нельзя использовать и система выполняет последовательное сканирование.

В примере полное сканирование вызывается тем, что DATE() применяется к каждой строке. Перепишите условие в виде предиката диапазона по самому столбцу (форме, допускающей использование индекса), и индекс по created_at начнёт использоваться.

То же относится к WHERE lower(email)=...: храните нормализованные данные, обращайтесь непосредственно к столбцу или создайте индекс по выражению.

-- Not sargable: index unusable
WHERE DATE(created_at) = '2026-01-01'

-- Sargable: range over the bare column
WHERE created_at >= '2026-01-01'
  AND created_at <  '2026-01-02'

Распространённая причина: отсутствующий индекс

Если основной узел — это Seq Scan с высокоселективным фильтром или вложенный цикл с огромным значением loops по внутреннему ключу без индекса, исправление обычно заключается в добавлении индекса.

Добавьте индекс по столбцу, используемому для фильтрации или соединения. В примере он создаётся по customer_id, чтобы соединение могло переключиться с последовательного сканирования на сканирование по индексу, а планировщик — выбрать гораздо более дешёвый план.

Проверьте результат, повторно выполнив EXPLAIN ANALYZE, — не предполагайте, что индекс помог.

CREATE INDEX idx_orders_customer
  ON orders (customer_id);

Распространённая причина: SELECT * и широкие строки

SELECT * извлекает с диска каждый столбец и передаёт его по сети, а также не позволяет выполнить сканирование только индекса, поскольку индекс редко содержит все столбцы.

Выбирайте только необходимые столбцы. Это уменьшает размер строки, снижает объём операций ввода-вывода и может сделать возможным сканирование только покрывающего индекса.

Если интервьюер специально добавил SELECT *, он хочет, чтобы вы это заметили. Сокращение списка столбцов часто даёт быструю и ощутимую пользу для широких таблиц.

-- Before
SELECT * FROM orders WHERE customer_id = 42;

-- After: only needed columns (may enable index-only scan)
SELECT order_id, amount FROM orders WHERE customer_id = 42;

Распространённая причина: выгрузка данных на диск

Если узел Sort или Hash сообщает об использовании диска (Sort Method: external merge Disk: 25000kB или Batches: > 1), операция превысила доступный объём work_mem и выгрузила данные на диск.

Возможные варианты: увеличить work_mem для сеанса, уменьшить количество строк, поступающих на сортировку или построение хеша (отфильтровать их раньше) либо добавить индекс, который обеспечивает нужный порядок сортировки и полностью устраняет необходимость в сортировке.

Это точный диагноз уровня старшего разработчика, который интервьюеры оценивают особенно высоко.

Sort  (actual rows=2000000 loops=1)
  Sort Key: o.amount
  Sort Method: external merge  Disk: 25000kB

Распространённая причина: извлечение лишних строк

Обратите внимание на Rows Removed by Filter: 9500000. Запрос прочитал десять миллионов строк и отбросил почти все — это классический пример напрасной работы.

Возможные исправления: добавить индекс, чтобы фильтр применялся во время доступа к данным, а не после него; сделать предикат более селективным или переместить фильтрацию на более ранний этап запроса, чтобы по дереву плана передавалось меньше строк.

Принцип прост: выполняйте как можно меньше работы, фильтруйте как можно раньше и с минимальными затратами.

Seq Scan on events
  Filter: (event_type = 'purchase')
  Rows Removed by Filter: 9500000

Диагностический контрольный список

Повторите это на собеседовании, и вы не собьётесь:

  • Измерьте с помощью EXPLAIN (ANALYZE, BUFFERS).
  • Найдите узел, который потребляет больше всего времени.
  • Сравните оценочное и фактическое количество строк, сначала исправьте устаревшую статистику.
  • Проверьте возможность использования индекса, удалите функции из столбцов, участвующих в фильтрации.
  • Добавьте индексы для селективных фильтров и ключей соединения.
  • Сократите список столбцов, избегайте SELECT *.
  • Следите за выгрузкой данных на диск и извлечением лишних строк.
  • Проверьте результат, повторно выполнив план.

Собираем всё вместе

Устно разберите полный пример. План показывает последовательное сканирование таблицы orders с 50 млн строк, фильтр customer_id = 42, значение Rows Removed by Filter около 50 млн; оценка примерно совпадает с фактическим результатом.

Диагностика: селективный фильтр, индекса нет, основная стоимость приходится на сканирование. Исправление: CREATE INDEX ON orders(customer_id). Повторный запуск: план переключается на сканирование по индексу, а время уменьшается с нескольких секунд до менее чем миллисекунды.

Этот цикл «измерить — диагностировать — исправить — проверить» — шаблон ответа на любой вопрос о медленном запросе.

CREATE INDEX idx_orders_customer ON orders (customer_id);
EXPLAIN (ANALYZE, BUFFERS)
SELECT order_id, amount FROM orders WHERE customer_id = 42;

Быстрая проверка

Запрос использует фильтр WHERE YEAR(order_date) = 2026, а план показывает полное последовательное сканирование, несмотря на существующий B-деревовидный индекс по order_date. Какое первое исправление будет лучшим?

Итоги

Теперь у Вас есть воспроизводимый метод ответа на вопросы о медленных запросах:

  • Всегда измеряйте с помощью EXPLAIN (ANALYZE, BUFFERS) и сосредоточьтесь на узле с наибольшей стоимостью.
  • Сначала исправляйте устаревшую статистику, если оценки расходятся с фактическими значениями.
  • Делайте предикаты пригодными для поиска по индексу, добавляйте индексы для селективных фильтров и ключей соединения, а также отказывайтесь от SELECT *.
  • Устраняйте сбросы данных на диск и избыточную выборку, затем проверяйте новый план.

Опишите этот список действий, предложите конкретное изменение и повторно запустите план, чтобы подтвердить результат — именно так отвечает опытный специалист.

Часто задаваемые вопросы

Урок «Поиск и исправление медленных запросов» бесплатный?

Да — полный текст урока «Поиск и исправление медленных запросов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Coding Interview Prep, подпишись на CoddyKit PRO. Курс Coding Interview Prep содержит 4 уроков всего.

Чему я научусь в уроке «Поиск и исправление медленных запросов»?

Диагностический список для вопроса на собеседовании: «этот запрос работает медленно, исправьте его» Ты практикуешь Coding Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Coding Interview Prep?

Предыдущий опыт не требуется. Coding Interview Prep на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Поиск и исправление медленных запросов»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Coding Interview Prep?

Да. Каждый урок Coding Interview Prep включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Чтение плана EXPLAIN
  2. Последовательное, индексное и покрывающее сканирование
  3. Алгоритмы соединения: вложенный цикл, хеширование и слияние
  4. Поиск и исправление медленных запросов
← Назад к Coding Interview Prep