awesome-everything EN
↑ Обратно к восхождению

Базы данных

Типы сканирования: Seq, Index, Bitmap, Index-Only

Суть Postgres выбирает среди четырёх типов сканирования на основе селективности и стоимости ввода-вывода. Чтение строки стоимости говорит о том, что предполагал планировщик и подтверждает ли это BUFFERS.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 14 min

EXPLAIN показывает Seq Scan на таблице из 10M строк, хотя индекс на колонке фильтрации существует. Планировщик не сломан — он подсчитал, что последовательное сканирование дешевле для данной селективности. Понимать, когда и почему планировщик выбирает тот или иной тип сканирования — вот что отличает угадывание от диагностики.

Чтение строки стоимости

Каждый узел плана печатает:

(cost=startup..total rows=N width=B)
  • startup — стоимость до выдачи первой строки. Ноль для Seq Scan; ненулевая для Sort (нужно прочитать всё первым делом).
  • total — стоимость полного выполнения узла.
  • rows — оценка строк, которые выдаст этот узел.
  • width — средняя ширина строки в байтах.

Стоимость измеряется в произвольных единицах, где 1.0 ≈ одно последовательное чтение страницы. Важны только соотношения; абсолютные числа без контекста бессмысленны. С ANALYZE каждый узел также получает (actual time=startup..total rows=N loops=L) — реальное wall time в миллисекундах.

Правило интерпретации: если узел находится на внутренней стороне Nested Loop, делите actual time на loops, чтобы получить стоимость одного выполнения.

Четыре типа сканирования

Тип сканированияПаттерн I/OПобеждает когда
Seq ScanПолная куча, последовательноПредикат совпадает со многими строками или нет пригодного индекса
Index ScanОбход индекса + чтение кучи на строку (случайный I/O)Мало совпадающих строк (<5-15% таблицы) и строки не кластеризованы
Bitmap Heap ScanОбход индекса → битовая карта TID → упорядоченное чтение кучиМного разрозненных строк (сотни — миллионы); преобразует случайный I/O в последовательный
Index Only ScanТолько листья индекса — без чтения кучиВсе нужные колонки в индексе И Visibility Map говорит, что страницы all-visible

Seq Scan

Формула стоимости: relpages × seq_page_cost + reltuples × cpu_tuple_cost. По умолчанию seq_page_cost = 1.0, cpu_tuple_cost = 0.01. Для таблицы в 5000 страниц: 5000 × 1.0 + 1M × 0.01 = 15,000 единиц. Оптимален, когда предикат неселективен (совпадает большинство строк) — читать всю кучу последовательно быстрее, чем случайно переходить к каждой совпадающей строке.

Index Scan

Стоимость растёт с random_page_cost × matching_rows. По умолчанию random_page_cost = 4.0 (эпоха HDD). Для 100 совпадающих строк: 100 × 4.0 = 400 — намного дешевле, чем Seq Scan за 15,000. Для 100,000 совпадающих строк: 100,000 × 4.0 = 400,000 — Seq Scan выигрывает. Точка пересечения примерно там, где предикат совпадает с 5–15% таблицы.

Замечание по настройке для SSD: на NVMe SSD случайные чтения лишь в 1.5–2× медленнее последовательных — не в 4×. Установите random_page_cost = 1.1 в postgresql.conf для SSD-систем. Планировщик будет предпочитать Index Scan для значительно большей доли запросов.

Bitmap Heap Scan

Двухэтапный процесс: сначала Bitmap Index Scan строит в памяти битовую карту всех совпадающих TID из индекса; затем Bitmap Heap Scan сортирует TID по физическому адресу страницы и читает кучу по порядку. Это преобразует разрозненные случайные чтения в почти последовательный обход кучи. Побеждает, когда совпадает много строк, но они разбросаны по куче — промежуток между Index Scan и Seq Scan.

Битовую карту можно объединять через OR для нескольких индексов (узел BitmapOr) — полезно, когда предикат OR имеет индекс на каждую колонку.

Index Only Scan

Читает только листья индекса — без чтения кучи — при двух условиях: (1) все нужные запросу колонки хранятся в индексе (как ключевые или через INCLUDE), и (2) Visibility Map подтверждает, что страница «all-visible» (каждый кортеж виден всем транзакциям). Когда бит VM не установлен, Postgres должен обратиться к куче для этой страницы. В выводе EXPLAIN ANALYZE значение Heap Fetches: N показывает, как часто это происходило — высокое N означает, что VM устарела и VACUUM давно не запускался.

BUFFERS: картина I/O

EXPLAIN (ANALYZE, BUFFERS) добавляет учёт буферов на каждом узле:

Seq Scan on orders  (actual time=0.1..450 rows=1000000 loops=1)
  Buffers: shared hit=4800 read=200
  • shared hit — страница найдена в кеше shared_buffers Postgres (без дискового I/O)
  • read — страница получена с диска или из ОС
  • dirtied — страница изменена этим узлом
  • written — страница сброшена на диск

Высокий read при низком hit означает, что рабочий набор больше shared_buffers или запрос обращается к холодным данным. Это диагностирует «медленно из-за CPU или I/O»: высокие reads = ограничен I/O; мало reads, но высокое actual time = ограничен CPU (обычно плохое соединение или тяжёлая сортировка).

Константы стоимости типов сканирования (умолчания Postgres)
seq_page_cost (умолчание)
1.0
random_page_cost (умолчание, для HDD)
4.0
random_page_cost (настройка для SSD)
1.1
cpu_tuple_cost
0.01
cpu_index_tuple_cost
0.005
Точка пересечения Index Scan vs Seq Scan (типично)
~5-15% строк таблицы
Ускорение Index Only Scan vs Index Scan
~10-100x на горячих чтениях
Викторина

Таблица из 1M строк имеет индекс на `status`. Запрос `WHERE status = 'active'` возвращает 400,000 строк (40% таблицы). Какой тип сканирования, скорее всего, выберет планировщик?

Викторина

EXPLAIN ANALYZE показывает `Index Only Scan ... Heap Fetches: 12000`. Что это означает?

Викторина

На NVMe SSD сервере вы оставляете `random_page_cost` со значением по умолчанию 4.0. Каков производственный эффект?

Вспомните перед уходом
  1. 01
    Объясните, почему планировщик выбирает Seq Scan на таблице из 1M строк, когда на колонке фильтрации есть индекс.
  2. 02
    Когда Bitmap Heap Scan выигрывает у обоих Index Scan и Seq Scan?
  3. 03
    Что такое вывод BUFFERS в EXPLAIN ANALYZE и как им диагностировать I/O vs CPU узкие места?
Итог

Postgres выбирает среди четырёх типов сканирования на основе стоимости: Seq Scan читает каждую страницу кучи последовательно и побеждает, когда совпадает большинство строк; Index Scan обходит B-дерево и читает строки кучи по одной, побеждая при селективном предикате (примерно <10% строк); Bitmap Heap Scan строит упорядоченную битовую карту TID и читает кучу по порядку, побеждая в среднем диапазоне селективности; Index Only Scan полностью пропускает кучу, когда все нужные колонки есть в индексе и Visibility Map актуальна. Планировщик использует константные стоимости — seq_page_cost, random_page_cost, cpu_tuple_cost — для сравнения вариантов. На NVMe SSD установите random_page_cost = 1.1, чтобы планировщик корректно предпочитал Index Scan для селективных предикатов. Используйте EXPLAIN (ANALYZE, BUFFERS), чтобы видеть, ограничен ли запрос I/O (много читаемых страниц) или CPU (высокое actual time при малом числе чтений).

Практика

Сделай это, чтобы превратить узнавание в навык.

Связанные уроки
встречается в174
Продолжить восхождение ↑Алгоритмы соединения и каскад ошибок оценки строк
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.