
Как читать первый план EXPLAIN для простого SELECT в PostgreSQL


Какой запрос и какую среду рассматриваем
Разберем один узкий сценарий: простой SELECT в PostgreSQL с фильтром по client_id. Цель не в том, чтобы подобрать «правильный» план для любой базы, а в том, чтобы по первой выдаче EXPLAIN понять, что именно планировщик собирается делать и что из увиденного является лишь оценкой.
Для первого чтения удобнее обычный EXPLAIN в текстовом формате. PostgreSQL строит план для каждого запроса, а EXPLAIN показывает выбранное дерево узлов. Текстовый вывод компактный: в нем сразу видно тип сканирования, прикрепленный фильтр и оценки по строкам и стоимости. Этого достаточно, чтобы не гадать, почему вы видите Seq Scan или Index Scan.
Важно заранее разделить два вопроса. Первый: что планировщик ожидает. На него отвечает обычный EXPLAIN. Второй: что реально произошло при выполнении. На него отвечает EXPLAIN ANALYZE, потому что он запускает запрос и показывает фактические показатели. В этой статье мы читаем именно первый план. Для изменяющих запросов такой порядок особенно важен: начинать безопаснее с EXPLAIN, а не с EXPLAIN ANALYZE, потому что ANALYZE выполняет оператор.
Как читать узлы в условном плане SELECT
План PostgreSQL — это дерево узлов. Внизу находятся узлы чтения данных: например, Seq Scan или Index Scan. В простом запросе без сортировки, соединений и агрегации часто будет всего один рабочий узел чтения таблицы. Поэтому первый план обычно читается сверху вниз буквально за несколько секунд.
Учебный пример. Возьмем условную таблицу orders и один запрос:
EXPLAIN
SELECT id, client_id, status
FROM orders
WHERE client_id = 42;В одной базе можно увидеть такой условный план:
Seq Scan on orders (cost=0.00..972.00 rows=1200 width=18)
Filter: (client_id = 42)А в другой — такой:
Index Scan using orders_client_id_idx on orders (cost=0.29..42.10 rows=12 width=18)
Index Cond: (client_id = 42)Главное различие здесь не в цифрах, а в форме записи условия. Filter означает, что узел читает строки-кандидаты и затем проверяет условие. Index Cond означает, что условие используется уже при поиске через индекс. Для первого чтения этого достаточно: если вы видите Filter на Seq Scan, значит проверка идет после чтения строк; если видите Index Cond, условие стало частью пути доступа.
Из этого не следует, что Seq Scan всегда плох. Если таблица небольшая или планировщик ожидает, что подойдет заметная доля строк, последовательное чтение может быть разумным выбором. Первая ошибка новичка — воспринимать тип узла как оценку качества без контекста. Сначала надо понять, почему этот узел выбран.
Что означают оценки rows, cost и width
В строке плана после имени узла идут четыре оценки: стартовая стоимость, полная стоимость, число выходных строк и средняя ширина строки. Важно, что это именно оценки планировщика. Они помогают сравнивать варианты исполнения, но не равны миллисекундам на вашем сервере.
| Фрагмент | Как читать |
|---|---|
cost=0.00..972.00 | Оценка затрат: сначала старт, затем стоимость выполнения узла до конца. |
rows=1200 | Сколько строк узел, по мнению планировщика, выдаст наружу. |
width=18 | Оценка средней ширины выходной строки в байтах. |
Самая полезная часть для первого чтения — rows. Это не число строк, которые узел обязательно просмотрит, а число строк, которое он должен выдать. В плане Seq Scan ... Filter: (client_id = 42) таблица может быть прочитана целиком, но наружу пойдут только те строки, которые проходят условие. Поэтому низкое rows не означает автоматически низкую работу по чтению.
С cost важно не перепутать назначение. Это внутренние условные единицы планировщика, зависящие от его параметров стоимости. PostgreSQL минимизирует именно их, а не «время на глаз». Кроме того, стоимость верхнего узла уже включает стоимость дочерних. Если в плане один узел, читать проще: его полная стоимость и есть оценка всего запроса.
Отдельно полезно помнить про width. Новичку оно редко помогает выбрать направление оптимизации, но объясняет, почему два похожих запроса с разным списком столбцов иногда получают разные оценки. Узкий результат и широкий результат могут отличаться по ожидаемым затратам, даже если фильтр одинаковый.
Как не перепутать оценку планировщика с фактическим временем
Когда человек впервые видит cost=972.00, он часто пытается мысленно перевести это в миллисекунды. Так делать не стоит. Планировщик считает относительную цену вариантов доступа, а не обещает, что запрос завершится за определенное время. На реальное время влияют факторы, которые в плане либо отражены косвенно, либо вообще не участвуют в выборе: состояние кеша, нагрузка на сервер, передача результата клиенту.
Поэтому полезно держать в голове простое правило. EXPLAIN отвечает на вопрос «что планировщик предполагает». EXPLAIN ANALYZE отвечает на вопрос «что произошло на самом деле». Если вы смешаете эти два уровня, появятся ложные выводы: например, «стоимость маленькая, значит запрос точно быстрый» или «стоимость большая, значит план плохой». Без фактического выполнения это всего лишь догадки.
В нашем учебном примере проверяемый результат такой: по одному текстовому плану уже можно точно установить, использован ли индекс как путь доступа или условие проверяется постфактум через Filter. А вот утверждать по одному EXPLAIN, сколько миллисекунд займет запрос, нельзя. Это и есть граница между чтением плана и измерением выполнения.
Практический ориентир: сначала назовите тип узла, потом место применения условия, потом оценку выходных строк. Только после этого переходите к вопросу о реальном времени.
Почему план нужно проверять в своей базе
Даже для простого фильтра по client_id план зависит от конкретной базы. PostgreSQL опирается на статистику, а статистика строится по выборке, поэтому оценки строк могут немного отличаться. Еще важнее распределение данных: один клиент может иметь несколько заказов, а другой — тысячи. С точки зрения планировщика это разные ситуации, и один и тот же запрос по форме не обязан получать один и тот же план.
Поэтому при чтении первого плана полезно задать себе три вопроса. Первый: сколько строк этот client_id обычно возвращает в моей таблице — единицы, сотни или значимую долю? Второй: есть ли вообще индекс, который может помочь именно этому условию? Третий: выглядят ли оценки rows правдоподобно по сравнению с моим знанием данных? Если на любой из этих вопросов ответ неясен, сам план еще не дает полной картины.
Именно здесь чтение EXPLAIN становится практичным. Вы не пытаетесь угадать скорость по одной цифре, а проверяете логику: планировщик считает, что строк будет мало или много; условие работает как фильтр или как условие индекса; выбранный путь соответствует моим данным или нет. Для первого контакта с планом этого достаточно, чтобы не сделать поспешный вывод.
Если хотите отдельно потренировать тему планов выполнения, в сохраненном описании каталога курса по PostgreSQL есть блок про типы индексов и анализ планов выполнения запросов с помощью EXPLAIN. Официальная страница школы в исходных данных недоступна, поэтому ориентироваться можно только на это сохраненное описание программы, без дополнительных обещаний.





















