Как оптимизатор PostgreSQL выбирает план
Как стоимость, статистика и корреляция данных помогают планировщику выбирать между последовательным и индексным сканированием.
PostgreSQLEXPLAINСтатистикаПроизводительность— Макс, ты уже говорил, что в некоторых случаях Seq Scan быстрее чем Index Scan! — напомнила Лена на следующий день. — Но везде пишут, что нужно избавляться от Seq Scan. Так как понять, когда Seq Scan хорошо, а когда плохо?
— А давай разберёмся, как Postgres вообще принимает такие решения. — отодвинул кружку с кофе Макс. — В этом нет никакой магии, обычная математика.
1. Как работает оптимизатор
Postgres оценивает планы через стоимость и статистику:
-
Стоимость — условная величина, а не миллисекунды. По умолчанию последовательное чтение одной страницы задаёт базовую единицу
seq_page_cost = 1; случайное чтение, обработка строк и операторов имеют собственные коэффициенты. Их соотношение описывает модель конкретного сервера лишь приблизительно. -
Статистика: для таблиц это прежде всего количество строк и информация о том, как распределены данные по столбцам:
-- Посмотрим статистику для таблицы orders:SELECT relpages, reltuples FROM pg_class WHERE relname = 'orders';→ relpages = 100 (блоков), reltuples = 10000 (строк)
2. Почему Seq Scan иногда быстрее
Пример запроса:
SELECT * FROM orders WHERE status = 'completed'; -- Допустим, 80% от всех строкУсловный Seq Scan:
-
Читает все 100 блоков (cost = 100).
-
Фильтрует строки в памяти (быстро).
Условный Index Scan:
-
Читает индекс (допустим, 20 блоков, cost = 20).
-
Для найденных ссылок обращается к страницам таблицы:
-
При слабой корреляции это может означать множество разрозненных обращений к одним и тем же или разным страницам.
-
Планировщик оценивает число страниц, кэширование и стоимость случайного доступа; это не равно числу строк, умноженному на
seq_page_cost.
-
Вывод: при возврате 80% таблицы последовательное чтение часто дешевле индексной навигации. Точные числа нужно брать из реального EXPLAIN, а не из этой упрощённой иллюстрации.
Конечно в процессе такого выполнения почти все блоки таблицы окажутся в кэше в памяти, и операция не будет реально эквивалентна чтению 8000 блоков. Но это уже отдельная тема.
— Действительно все просто и логично, — отметила Лена. — Выбирается вариант с наименьшими затратами.
— Да. Оптимизатор учитывает еще множество разных деталей. Например, разницу между последовательным и случайным чтением, корреляцию индекса и т.д.
— А что это такое “корреляция индекса”? — заинтересовалась Лена.
— Это такая статистика, которая показывает, насколько физический порядок строк в таблице соответствует реальному порядку в данных. Это важно, когда есть индекс и выборка идет по диапазону (BETWEEN). Может иметь значения от -1 до 1:
-
1.0 — сильное совпадение физического и возрастающего логического порядка.
-
0.0 — нет корреляции.
-
-1.0 — столь же сильный, но обратный порядок.
-- Посмотреть correlation для столбца:SELECT tablename, attname, correlation FROM pg_statsWHERE tablename = 'orders' AND attname = 'created_at';Эта информация используется для оценки стоимости Index Scan:
-
Если модуль
correlationблизок к 1.0, диапазонный Index Scan может читать страницы более последовательно, в прямом или обратном направлении; -
Если correlation ближе к 0.0, то Index Scan потребует много случайных чтений, что повышает cost.
Конечно, на современных SSD разница между последовательным и случайным чтением не такая большая, как на обычных дисках, но она все же есть.
— На физический порядок можно повлиять командой CLUSTER, — добавил Макс. — Она однократно переписывает таблицу по выбранному индексу, требует дополнительное место и эксклюзивную блокировку, а последующие изменения снова размывают порядок. Улучшив корреляцию одного ключа, можно ухудшить другой. Поэтому сначала нужен измеримый выигрыш и отдельный план обслуживания.
— Поняла! — Лена сделала заметку в блокноте. — Значит, главное не слепо гнаться за индексами, а понимать логику оптимизатора.
— Хорошо сказано! — Макс одобрительно кивнул. — Для закрепления предлагаю тебе поизучать статистику для известных тебе таблиц. В pg_stats еще много информации. Готовь вопросы - обсудим.