Поиск и исправление медленных запросов
Диагностический список для вопроса на собеседовании: «этот запрос работает медленно, исправьте его»
«Поиск и исправление медленных запросов» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Чтение плана EXPLAIN
- Последовательное, индексное и покрывающее сканирование
- Алгоритмы соединения: вложенный цикл, хеширование и слияние
- Поиск и исправление медленных запросов