Последовательное, индексное и покрывающее сканирование
Почему планировщик выбирает каждый вариант и что это говорит о вашем запросе
«Последовательное, индексное и покрывающее сканирование» — бесплатный урок Coding Interview Prep на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Coding Interview Prep, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Coding Interview Prep содержит 4 уроков всего.
Три способа прочитать таблицу
Когда планировщику нужны строки из таблицы, он выбирает один из трёх методов доступа, и на собеседовании от Вас ожидают, что Вы назовёте все три:
- Последовательное сканирование — прочитать каждую строку таблицы от начала до конца.
- Сканирование по индексу — пройти по индексу, найти совпадающие строки, а затем получить каждую из таблицы.
- Сканирование только индекса — получить ответ полностью из индекса, вообще не обращаясь к таблице.
Понимание того, почему планировщик выбирает каждый из этих методов, — основа данного урока и гарантированный вопрос для опытного специалиста.
Что делает последовательное сканирование
Последовательное сканирование читает страницы таблицы одну за другой и применяет фильтр к каждой строке. Индекс не используется.
Это звучит плохо, но часто является правильным выбором. Последовательное чтение быстро выполняется диском (без случайных переходов), поэтому, когда запрос возвращает значительную часть таблицы, прочитать всё оказывается эффективнее, чем миллионы раз обращаться к индексу.
Пример: просканировать orders и оставить строки, где amount > 100. Если большинство заказов превышает 100, последовательное сканирование будет правильным выбором.
EXPLAIN SELECT * FROM orders WHERE amount > 100;
Seq Scan on orders (cost=0.00..18334.00 rows=900000 width=64)
Filter: (amount > 100)Что делает сканирование по индексу
Сканирование по индексу использует B-дерево, чтобы сразу перейти к совпадающим ключам, а затем читает соответствующие строки из кучи таблицы.
Оно особенно эффективно, когда фильтр избирательный и возвращает небольшую часть таблицы. Поиск 5 строк через индекс эффективнее чтения 10 миллионов строк.
В плане указано имя использованного индекса. Каждое совпадение требует одного обращения к индексу и одного чтения из кучи (случайного чтения), поэтому сканирование по индексу теряет преимущество, когда возвращает слишком много строк.
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;
Index Scan using idx_orders_customer on orders
(cost=0.42..38.50 rows=12 width=64)
Index Cond: (customer_id = 42)Селективность определяет выбор
Главное понятие, определяющее всё это, — селективность: доля строк, которые оставляет предикат.
- Высокая селективность (совпадает мало строк, например при уникальном идентификаторе) благоприятствует сканированию по индексу.
- Низкая селективность (совпадает много строк, например при
status IS NOT NULL) благоприятствует последовательному сканированию.
Распространённое ориентировочное правило: когда запрос возвращает более 5–10 процентов таблицы, планировщик часто предпочитает последовательное сканирование, поскольку случайные чтения кучи при использовании индекса становятся дороже, чем последовательное чтение всей таблицы.
Нюанс сканирования только индекса
На собеседованиях любят этот нюанс. Сканирование только индекса всё равно должно подтвердить, что каждая строка видима для Вашей транзакции (MVCC), а сам индекс не хранит сведения о видимости.
Postgres использует карту видимости: если страница помечена как полностью видимая, СУБД пропускает кучу; если нет, ей всё равно приходится получать строку из кучи. План показывает Heap Fetches: N.
Именно поэтому в недавно обновлённой таблице может быть много чтений кучи, а сканирование только индекса может работать медленно, пока VACUUM не обновит карту видимости.
EXPLAIN SELECT customer_id FROM orders WHERE customer_id = 42;
Index Only Scan using idx_orders_customer on orders
(cost=0.42..8.44 rows=12 width=4)
Index Cond: (customer_id = 42)Нюанс с картой видимости
Интервьюеры любят этот нюанс. Даже при сканировании только индекса необходимо подтвердить, что каждая строка видима для вашей транзакции (MVCC), а в самом индексе информация о видимости не хранится.
Postgres использует карту видимости: если страница отмечена как полностью видимая, он пропускает кучу; если нет, ему всё равно приходится извлекать строку из кучи. В плане отображается Heap Fetches: N.
Поэтому в только что обновлённой таблице может быть много обращений к куче, а сканирование только индекса может работать медленно, пока VACUUM не обновит карту видимости.
Index Only Scan using idx_orders_customer on orders
(actual time=0.01..0.03 rows=12 loops=1)
Heap Fetches: 0Битовые сканирования: компромиссный вариант
Часто встречается и четвёртый метод: сканирование кучи по битовой карте. Планировщик выбирает его, когда предикат совпадает с большим числом строк, чем эффективно обрабатывать обычным сканированием по индексу, но с меньшим, чем при чтении всей таблицы.
Сначала из индекса строится битовая карта расположения совпадающих строк (индексное сканирование по битовой карте), затем страницы кучи извлекаются в физическом порядке, а не в случайном. Упорядоченные чтения намного дешевле разрозненных чтений при обычном сканировании по индексу.
Bitmap Heap Scan on orders (cost=12.0..520.0 rows=8000)
Recheck Cond: (status = 'pending')
-> Bitmap Index Scan on idx_orders_status
(cost=0..12 rows=8000)
Index Cond: (status = 'pending')Почему планировщик проигнорировал Ваш индекс
Классический вопрос на собеседовании: я добавил индекс, но план всё равно выполняет последовательное сканирование — почему? Распространённые причины:
- Предикат неизбирательный, поэтому сканирование действительно дешевле.
- К столбцу применяется функция:
WHERE lower(email) = ...нельзя выполнить с помощью обычного индекса поemail. - Несоответствие типов приводит к неявному приведению типа, из-за которого индекс не используется.
- Статистика устарела — выполните
ANALYZE. - Таблица очень маленькая, поэтому чтение нескольких страниц эффективнее накладных расходов индекса.
Разбор диагностики
Предположим, что в orders есть индекс по created_at, но этот запрос всё равно выполняет последовательное сканирование:
Причина — DATE(created_at). Применение функции к столбцу означает, что индекс по исходному created_at нельзя использовать. Исправление — переписать запрос с помощью диапазонного предиката, оставив столбец без изменений, или создать индекс по выражению DATE(created_at).
-- Slow: function on the indexed column
WHERE DATE(created_at) = '2026-01-01'
-- Fast: bare column, range uses the index
WHERE created_at >= '2026-01-01'
AND created_at < '2026-01-02'Сравнение методов
Запомните это сравнение для собеседования:
- Последовательное сканирование — лучший вариант при возврате большой доли строк; последовательный ввод-вывод.
- Сканирование по индексу — лучший вариант для избирательного поиска; проход по индексу и случайные чтения кучи.
- Сканирование кучи по битовой карте — среднее число совпадений; сначала индекс и битовая карта, затем упорядоченное чтение кучи.
- Сканирование только индекса — самый быстрый вариант, когда индекс содержит все нужные столбцы, а страницы полностью видимы.
Планировщик выбирает метод по оценочной стоимости, которая в основном определяется селективностью и статистикой.
Принудительная проверка (и почему не в рабочей среде)
Чтобы проверить гипотезу в среде разработки, можно временно повлиять на планировщик: SET enable_seqscan = off; заставит его отдавать предпочтение индексам, чтобы Вы могли сравнить планы.
Это диагностический приём, а не исправление для рабочей среды. На собеседовании упомяните, что настоящие решения — это более точная статистика, подходящий индекс или переписывание предиката, а не глобальное отключение возможностей планировщика.
SET enable_seqscan = off;
EXPLAIN ANALYZE SELECT * FROM orders WHERE amount > 100;
SET enable_seqscan = on;Быстрая проверка
Запрос выбирает только email и фильтрует по email, а для email существует индекс B-дерева. План показывает Index Only Scan. Почему это быстрее обычного сканирования по индексу?
Итоги
Главные выводы о методах доступа:
- Последовательное сканирование выигрывает для запросов с низкой селективностью, а сканирование по индексу — для избирательных запросов.
- Сканирование только индекса не обращается к куче, если индекс содержит все нужные столбцы; следите за
Heap Fetchesи картой видимости. - Сканирование кучи по битовой карте занимает промежуточное положение, извлекая страницы кучи в физическом порядке.
- Планировщик принимает решение по селективности и статистике; функции над столбцами, несоответствие типов и устаревшая статистика — причины, по которым индекс игнорируется.
Часто задаваемые вопросы
Урок «Последовательное, индексное и покрывающее сканирование» бесплатный?
Да — полный текст урока «Последовательное, индексное и покрывающее сканирование» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Coding Interview Prep, подпишись на CoddyKit PRO. Курс Coding Interview Prep содержит 4 уроков всего.
Чему я научусь в уроке «Последовательное, индексное и покрывающее сканирование»?
Почему планировщик выбирает каждый вариант и что это говорит о вашем запросе Ты практикуешь Coding Interview Prep с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Coding Interview Prep?
Предыдущий опыт не требуется. Coding Interview Prep на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Последовательное, индексное и покрывающее сканирование»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Coding Interview Prep?
Да. Каждый урок Coding Interview Prep включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Чтение плана EXPLAIN
- Последовательное, индексное и покрывающее сканирование
- Алгоритмы соединения: вложенный цикл, хеширование и слияние
- Поиск и исправление медленных запросов