Сайт использует сookies для хранения данных. Продолжая использовать сайт, вы даёте согласие на работу с этими файлами.

ОК
🧱
Данные
Опубликовано:
23.07.2026
Обновлено:
03.08.2026

Медленные запросы в PostgreSQL: чек-лист на первые 30 минут инцидента

Данил Мануйлов

Когда приложение начинает тормозить, а мониторинг показывает аномальный рост времени ответа базы данных, каждая минута простоя стоит денег и нервов. Задача первых тридцати минут - не выдать идеальное решение, а локализовать проблему, снизить остроту и подготовить почву для плановой оптимизации. Этот материал - практическая последовательность шагов, которую можно выполнять прямо во время инцидента.

Почему первые 30 минут решают всё

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

Шаг 1. Найти проблемные запросы

Пока пользователи пишут в чат «всё медленно», вы находите конкретных виновников.

pg_stat_activity - кто висит прямо сейчас

Первое, что стоит проверить, - активные и ожидающие сессии. Запрос к pg_stat_activity покажет запросы, выполняющиеся дольше обычного, их состояние и время старта. Обратите внимание на статусы active, idle in transaction и waiting. Подозрительные сессии - те, что висят десятки секунд или минут там, где ожидается отклик в миллисекундах.

Полезно сразу отфильтровать системные процессы и посмотреть на пользовательские соединения. Если один и тот же запрос повторяется в нескольких строках с разными параметрами - это первый кандидат на углублённый анализ.

pg_stat_statements - самые тяжёлые запросы за всё время

Если расширение pg_stat_statements включено, вы получаете агрегированную картину по всем нормализованным запросам. Для поиска проблем смотрите на столбцы mean_exec_time (среднее время выполнения) и total_exec_time (суммарное время). Запросы с высоким средним временем и большим количеством вызовов почти наверняка участвуют в деградации.

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

Журналы медленных запросов (log_min_duration_statement)

Если логирование медленных запросов настроено (например, порог 500 мс или 1 секунда), просмотрите журнал за последние минуты. Ищите повторяющиеся паттерны или запросы, которые раньше не попадали в slow log, а теперь заполонили его. Это быстрый способ подтвердить, что проблема действительно в конкретном типе операций, а не в общей деградации сервера.

Шаг 2. Прочитать план выполнения

Найдя подозрительный запрос, следующий обязательный шаг - заглянуть в его план выполнения.

EXPLAIN (ANALYZE, BUFFERS) - без него никуда

Выполните EXPLAIN (ANALYZE, BUFFERS) для проблемного запроса (если это безопасно для текущей нагрузки - например, на реплике или с ограничением по времени через statement_timeout). Анализируйте не предполагаемую стоимость, а фактические времена и количество строк по каждому узлу. Именно расхождение между ожидаемым и реальным поведением чаще всего указывает на корень проблемы.

Три главных тревожных сигнала в плане

  1. Оценка числа строк отличается на порядок и более.
    Если планировщик ожидал 100 строк, а реально пришло 100 000, он выбрал неподходящий тип соединения или порядок операций. Такое случается при устаревшей статистике или сильно неравномерном распределении данных в столбцах.

  2. Появление Seq Scan на больших таблицах там, где должен быть Index Scan.
    Последовательное сканирование многомиллионной таблицы при каждом вызове запроса - частая причина внезапного замедления. Причиной может быть отсутствие нужного индекса, блокировка его использования из-за наложения функций на колонку в WHERE или слишком большой ожидаемый процент выбираемых строк.

  3. Узел-рекордсмен по времени исполнения.
    Найдите в дереве плана узел, который отъедает львиную долю времени. Это и есть ваше бутылочное горлышко. Оно подскажет, куда направить усилия - добавить индекс, переписать условие, разбить запрос или обновить статистику.

Шаг 3. Проверить статистику и обслуживание таблиц

Планировщик PostgreSQL полагается на актуальность статистики и физическое состояние данных.

Когда ANALYZE не делали сто лет

Если оценки селективности в плане сильно ошибаются, скорее всего, статистика по одной или нескольким таблицам устарела. Проверьте, когда в последний раз запускался ANALYZE для этих таблиц (можно косвенно оценить по изменениям количества строк после последнего автоанализа или по системным каталогам). Если таблицы активно изменялись, а ANALYZE не выполнялся, принудительно запустите его - в большинстве случаев это безопасно и быстро.

Для колонок с перекошенным распределением значений (например, статус заказа: 99% завершённых и 1% новых) стоит задуматься об увеличении ALTER TABLE ... SET STATISTICS после завершения инцидента, чтобы планировщик собирал более детальную гистограмму.

«Раздутость» (bloat) - быстрый взгляд и что с ней делать без остановки сервиса

Таблицы и индексы со временем могут «распухать» из-за особенностей MVCC: обновлённые и удалённые строки не освобождают место сразу. Чрезмерная раздутость заставляет сканировать больше страниц, увеличивает I/O и замедляет запросы. В первые 30 минут можно оценить масштаб бедствия с помощью запросов к pg_stat_user_tables (сравнивая n_live_tup и n_dead_tup) или специализированных скриптов.

Если раздутость признана вероятной причиной тормозов, в экстренном порядке можно выполнить VACUUM (не FULL) на проблемной таблице - он не блокирует конкурентный доступ. Полноценную переупаковку через pg_repack или VACUUM FULL оставьте на период плановых работ после инцидента, так как они требуют длительных блокировок или создают копию данных.

Шаг 4. Индексы: есть, нет, не используются

Даже когда индексы физически присутствуют, запрос может игнорировать их по разным причинам.

Как за минуту проверить, что индексы подходят под условия

Выведите структуру таблицы в psql (\d table_name) и сопоставьте столбцы индексов с теми, которые используются в фильтрах WHERE и соединениях JOIN. Если запрос фильтруется по колонке, которой нет ни в одном индексе, и таблица большая - вы нашли точку приложения усилий. Убедитесь также, что тип индекса соответствует типу операций (B-tree для равенств и диапазонов, GIN для полнотекстового поиска и т.д.).

Частые ошибки в запросах, убивающие индексы

  • Применение функций к индексированной колонке: WHERE LOWER(name) = 'john'. Индекс по name не будет задействован. Решение - функциональный индекс CREATE INDEX ON users (LOWER(name)) или переписывание условия.
  • Использование LIKE '%text%' с ведущим символом подстановки. B-tree индекс тут бесполезен, нужен GIN/триграмный индекс.
  • Ожидание, что индекс по (a, b) ускорит запрос с условием только по столбцу b. В PostgreSQL это возможно только при определённых условиях (Index Skip Scan появился с 17 версии), но в общем случае придётся создавать отдельный индекс или пересматривать порядок столбцов.

Добавить индекс срочно, но аккуратно (CREATE INDEX CONCURRENTLY)

Если вы точно определили отсутствующий индекс и его добавление способно кардинально снизить нагрузку, создавайте его с опцией CONCURRENTLY. Это позволит новому индексу строиться без долгой эксклюзивной блокировки таблицы. Учтите, что создание индекса всё равно потребляет ресурсы процессора и диска, поэтому в разгар инцидента оцените, не добьёт ли эта операция сервер окончательно. В крайнем случае можно запустить построение на реплике, а после переключиться на неё.

Шаг 5. Блокировки и конкуренция

Медленные запросы не всегда виноваты сами - иногда они становятся жертвами ожидания освобождения ресурсов.

Кто кого заблокировал и как это увидеть

Используйте pg_stat_activity, но теперь с прицелом на столбцы wait_event_type и wait_event. Если видите много сессий с Lock или LWLock, значит, идёт борьба за блокировки. Связка pg_locks с pg_stat_activity покажет, какие транзакции удерживают блокировки и какие их ждут. Типичный сценарий: длительная транзакция, которая обновляет строку и не завершается, блокирует всех остальных, работающих с той же таблицей.

В первые 30 минут не стоит сразу убивать «мешающую» транзакцию, если не уверены на 100% в её безболезненном откате. Но подсветить проблему для дальнейшего разбора - обязательно.

Высокий CPU и диск - не всегда запрос-убийца

Посмотрите на утилизацию ресурсов сервера в целом. Если процессор загружен на 90% и iowait низкий, вероятно, множество неоптимальных запросов создают избыточную вычислительную нагрузку. Если iowait зашкаливает, а дисковая подсистема медленная, даже простые запросы могут упираться в скорость чтения‑записи. Проверьте, нет ли аномально частой записи в WAL или операций временных файлов (запросы с сортировкой, созданием хешей на диске). Это даст понять, надо ли смотреть в сторону work_mem или физических характеристик дисков.

Шаг 6. Быстрый осмотр конфигурации

Неправильные настройки могут сделать нормальные запросы медленными даже при хорошей статистике и индексах.

Параметры памяти, которые стоит проверить сразу

  • shared_buffers - размер кеша страниц. Если он выставлен слишком низко (например, стандартные 128 МБ при десятках гигабайт данных), большая часть чтений уходит на диск.
  • work_mem - память для сортировок и хеш-таблиц на одну операцию. Если запросы используют много сортировок или Hash Join, а work_mem маловато, PostgreSQL сбрасывает данные на диск, и запрос резко замедляется. Увеличивать его прямо в бою стоит очень осторожно: несколько одновременных сессий с высоким work_mem могут вызвать OOM killer.
  • effective_cache_size - оценка памяти, доступной для кеширования на уровне ОС. Не влияет напрямую на потребление, но задаёт планировщику ориентир при выборе между индексным и последовательным сканированием. Неверное значение может склонять планировщик к Seq Scan даже при наличии индекса.

Чего ни в коем случае не делать в первые 30 минут

  • Не менять фундаментальные настройки сервера (перезагрузка, shared_buffers) без чёткого обоснования и без возможности быстро откатить.
  • Не выполнять VACUUM FULL или REINDEX на горячем проде без крайней нужды.
  • Не сбрасывать статистику pg_stat_statements без предварительного сохранения - потеряете историческую картину, которая может пригодиться через 20 минут.
  • Не убивать все активные соединения скопом, особенно если не знаете, какие из них держат важные транзакции.

Что дальше: стабилизация и план действий после первого получаса

Первые 30 минут - это разведка. К этому моменту у вас уже должны быть на руках:

  • один или несколько подозрительных запросов;
  • их планы выполнения с явными аномалиями;
  • понимание, связана ли проблема с отсутствием индекса, устаревшей статистикой, блокировками или нехваткой памяти.

Дальнейшие шаги зависят от первопричины. Если виновата статистика - ANALYZE и временное отключение проблемного запроса на прикладном уровне (если возможно). Если отсутствует индекс - построение с CONCURRENTLY в плановом окне или, при крайней необходимости, немедленно на реплике. Если блокировки - координация с разработчиками для ускорения или отмены зависшей транзакции.

Главное - задокументируйте наблюдения сразу, пока память свежая: время начала, список запросов, скриншоты планов, выводы. Это послужит отправной точкой для полноценного разбора инцидента (post-mortem) и предотвращения повторения в будущем.

FAQ: Медленные запросы в PostgreSQL - первые минуты

Как за 1 минуту найти самый медленный запрос в PostgreSQL?
Запросите pg_stat_statements с сортировкой по mean_exec_time DESC или total_exec_time DESC и ограничением в 5–10 строк. Это покажет запросы с наибольшим средним и суммарным временем выполнения.

Почему EXPLAIN показывает одно, а реальное время другое?
EXPLAIN без ANALYZE оперирует оценками планировщика, которые могут сильно расходиться с реальностью из-за устаревшей статистики или неточного предсказания числа строк. Всегда используйте EXPLAIN ANALYZE, чтобы видеть фактические цифры.

Что делать, если Seq Scan на большой таблице, а индекс есть?
Проверьте, не применяется ли функция к индексированной колонке в WHERE, нет ли оператора LIKE '%...', соответствует ли порядок столбцов в составном индексе условиям запроса. Если всё чисто, но планировщик упорно выбирает последовательное сканирование, оцените настройки random_page_cost и effective_cache_size - возможно, они дезориентируют планировщик.

Как часто нужно делать ANALYZE?
Обычно достаточно настроек автоочистки (autovacuum), но после массовых изменений в таблице (загрузка, массовое обновление, удаление) стоит принудительно выполнить ANALYZE. Точной универсальной периодичности нет - ориентируйтесь на расхождения в планах запросов.

Можно ли создать индекс в продакшене без простоя?
Да, конструкция CREATE INDEX CONCURRENTLY строит индекс без длительной эксклюзивной блокировки таблицы, что позволяет продолжать чтение и запись. Однако операция потребляет ресурсы, поэтому лучше выполнять её в период пониженной нагрузки.

Чем опасно резкое изменение work_mem во время инцидента?
Параметр действует на каждую операцию сортировки или хеширования, и при умножении на количество одновременных запросов суммарное потребление памяти может превысить физический объём RAM, что вызовет вызов OOM killer и падение сервера. Меняйте его только с точным пониманием текущего числа параллельных сессий.

Нужно ли перезагружать PostgreSQL, если запросы начали тормозить?
Нет, перезагрузка в слепую - худшее, что можно сделать. Вы потеряете текущие сессии, кеш буферов и всю картину инцидента. Сначала диагностируйте причину по шагам выше.

Вывод

Первые полчаса при расследовании медленных запросов в PostgreSQL - это не время для героических правок конфигурации, а момент спокойной и методичной диагностики. Последовательно проверьте проблемные запросы, их планы, статистику, индексы, блокировки и базовые параметры памяти. Почти всегда хотя бы одна из этих проверок указывает на первопричину. Действуйте по чек-листу, фиксируйте находки и только потом переходите к исправлению - так вы не превратите локальный сбой в масштабную аварию.

Источники

Это авторская статья, основанная на личном опыте и субъективном взгляде автора. Заметили ошибку или битую ссылку? Сообщите нам: info@codesrc.ru - мы оперативно исправим. Спасибо, что помогаете делать блог лучше.
Следите за нами в соцсетях:

Читайте также