Чтение плана запроса
В прошлом уроке мы предложили индекс по теме, но не обещали, что база выберет его для шести строк. Чтобы обсуждать способ выполнения, нужен план запроса. План показывает операции, которыми PostgreSQL собирается получить результат: прочитать строки, отобрать их, отсортировать и ограничить ответ. Чтение такого дерева помогает связать SQL с работой базы, не заменяя измерение догадками.
Используем PostgreSQL 17.11 и снимок catalog-db/lesson-15 из архива серии. SQL и EXPLAIN пока не запускались. Файл plan-shape.txt содержит только схематическую форму, а не скопированный ответ сервера. В ней нет выдуманных cost, фактических времён, количества буферов или результатов измерения.
Запрос и предполагаемые этапы
Публичная лента выбирает три последних опубликованных курса. Порядок задаётся временем и идентификатором, поскольку два курса опубликованы одновременно:
EXPLAIN (VERBOSE, COSTS OFF)
SELECT id, slug, published_at
FROM catalog.courses
WHERE status = 'published'
ORDER BY published_at DESC, id DESC
LIMIT 3;
Предметный результат самого SELECT должен содержать SEO, PostgreSQL и Markdown. Но EXPLAIN без ANALYZE не возвращает эти карточки и не выполняет обычную выборку для измерения. Он сообщает выбранный план. Это различие важно: ответ из строк плана нельзя показывать на странице как будто приложение уже прочитало курсы.
Возможная форма на маленьком наборе выглядит так:
СХЕМА, а не фактический вывод PostgreSQL:
Limit
Sort по published_at DESC, id DESC
Seq Scan по catalog.courses
Filter: status = 'published'
Она иллюстрирует зависимости: чтение предоставляет строки, отбор оставляет опубликованные, сортировка определяет порядок, ограничение отдаёт нужный фрагмент. Реальный PostgreSQL может выбрать другую форму при иной структуре, статистике или условиях. Схема помогает освоить чтение, а не доказывает наличие именно этих узлов в вашей среде.
VERBOSE просит дополнительные детали представления. COSTS OFF убирает оценки стоимости и связанные оценочные поля из вывода, чтобы первый раз сосредоточиться на структуре. В отдельном EXPLAIN с обычными настройками оценки доступны, но их нельзя заранее заполнить числами из другого компьютера. Правила изложены в руководстве EXPLAIN PostgreSQL.
Читаем дерево снизу к потребителю
В текстовом представлении вложенные операции обозначаются отступами. Верхний узел получает данные от нижних. Чтобы понять судьбу одной строки, удобно сначала прочитать источник, затем действия над его результатом. Это смысловая зависимость, а не обещание, что каждая операция всегда полностью заканчивается до начала следующей: исполнение может передавать строки постепенно.
Seq Scan означает последовательное чтение таблицы с проверкой условия. Такое название не равно «плохой план». Для маленькой таблицы оно может быть естественным выбором. Если условие возвращает значительную долю строк большой таблицы, последовательное чтение тоже может быть разумнее отдельных обращений через индекс.
Index Scan использует индекс для доступа к строкам. Но одного этого слова недостаточно, чтобы объявить запрос быстрым. Нужно посмотреть, какую часть условия обслуживает доступ, сколько кандидатов рассматривается и какие дополнительные операции нужны. Если после индексного поиска остаётся много отбрасываемых строк, причина может быть в несоответствии ключей конкретному сценарию.
В планах можно увидеть Index Cond и Filter. Первое показывает условие, применяемое на индексном доступе, второе — дополнительную проверку строки на соответствующем этапе. Различие помогает объяснить, почему формально существующий индекс не всегда исключает большую часть лишней работы. Оно также направляет следующий вопрос: какой вход получает верхний узел?
Оценка отличается от измерения
Обычный EXPLAIN содержит оценочные строки и стоимость. Стоимость выражена во внутренних единицах планировщика, а не в миллисекундах. Оценочное число строк отражает представление базы о распределении данных. Если оно неверно, выбор способа работы может быть неудачным, но узнать фактическое число только из обычной оценки нельзя.
EXPLAIN ANALYZE запускает исследуемый запрос и добавляет сведения исполнения. В текущих учебных файлах его нет: пользователь просил не выполнять программы и измерения. При будущей самостоятельной работе нужно понимать, что ANALYZE относится к самой команде, которую вы анализируете. Для изменяющего SQL это уже изменение данных, а не безобидный просмотр текста.
Даже для чтения одно измерение не даёт универсальную скорость. Кеши, конкурентная нагрузка, число возвращаемых строк и состояние данных влияют на наблюдение. Для разумного сравнения требуется одинаковый сценарий и сохранённые условия. На шести курсах различия слишком малы, чтобы делать вывод о большом веб-приложении по красивым десятичным цифрам.
Поля циклов и фактических строк в измеренном плане также требуют чтения по правилам конкретного формата. Нельзя автоматически складывать все показанные времена как независимые расходы: родительский узел связан с работой дочерних. Здесь не приводим арифметику неподтверждённого измерения, а отмечаем причину, по которой любой численный отчёт нужно разбирать вместе с деревом.
Сортировка и ограничение
Наличие LIMIT 3 не гарантирует, что база коснётся только трёх исходных строк. Если нужного упорядоченного доступа нет, сначала может понадобиться рассмотреть подходящие строки и определить первые по сортировке. Ограничение говорит о размере ответа, а способ получения зависит от плана. Это особенно заметно в лентах больших таблиц.
Составной индекс, соответствующий условию и порядку, потенциально меняет доступ. Такой кандидат будет в следующем уроке. Но после создания мы всё равно читаем план и не предполагаем заранее исчезновение сортировки. Размер и распределение данных, а также точное направление ключей остаются частью решения.
Снимок также содержит EXPLAIN запроса соединения курсов с темами. Там возможны узлы соединения, и сравнивать их нужно по числу и форме входов. Выбор Hash Join, Nested Loop или другого метода нельзя оценить одним правилом «всегда хорошо» или «всегда плохо». На маленькой таблице простая стратегия часто соответствует задаче без сложной подготовки.
План не меняет договор результата
Два разных плана могут вернуть один и тот же правильный набор. Оптимизация не должна менять опубликованный фильтр, порядок равных моментов и смысл ограничения ради более короткого дерева. Если убрать второй ключ сортировки, SQL станет проще, но порядок Markdown и PostgreSQL перестанет иметь прежнее обещание. Это изменение поведения, а не чистое ускорение.
План и передача результата
Измерение работы базы не включает автоматически весь путь до читателя. После выборки приложение может преобразовать строки в JSON, отправить их по сети и построить карточки в браузере. Медленная страница не всегда означает медленный SQL. План помогает исследовать конкретную часть системы и сохранить границы вывода.
Похожее различие есть у размера ответа. LIMIT ограничивает число строк, но выбранные колонки могут содержать большие тексты. В данном запросе мы берём только ключ, код и время; bodies уроков вообще не читаются. Это соответствует задаче ленты и делает договор передачи понятным. Если потом добавить полные тексты всех глав, изменится не только видимый список полей, но и объём работы системы.
Поэтому при будущем исследовании фиксируйте точный SQL вместе с условиями и ожидаемой формой ответа. Измерять сокращённую лабораторную выборку, а затем приписывать её время другому широкому запросу означало бы сравнить разные задачи. Схема плана на этой странице намеренно не подменяет такой журнал.
При будущем ручном изучении сначала назовите ожидаемые три карточки, потом прочитайте форму EXPLAIN и объясните источник строк, условие и верхнего потребителя. Отдельно отметьте, где вы видите оценки, а где могли бы появиться фактические данные. Теперь обсуждение индекса может опираться на понятный план, сохраняя честную границу между рассуждением, ожидаемым результатом и ещё не проведённым измерением.