Материалы / Технические статьи / Index Cond и Filter в плане
Статья

Index Cond и Filter в плане

Как отличить навигацию по индексу от последующей фильтрации и переписать условия, чтобы составной индекс использовался эффективнее.

— Макс, я сломала мозг, — подошла Лена. — Смотри: у меня есть составной индекс по (status, created_at). Я делаю запрос с условиями по обоим этим полям. Но в плане запроса поле status попадает в Index Cond, а created_at — в Filter. Почему он не использует индекс целиком? Это же неэффективно!

Макс отвлекся от своего монитора.

— Поздравляю. Ты только что нашла «серую зону» оптимизации, где запрос вроде бы использует индекс, но не на все сто. Это как купить швейцарский нож и использовать его только для открывания бутылок.

— То есть Filter — это плохо?

— Ну не то чтобы это плохо, но это важный сигнал. Давай на аналогии. Представь, что индекс — это картотека в библиотеке.

  • Index Cond (условие индекса): Ты подходишь к ящику с буквой «Т», потому что ищешь Толстого. Это мгновенный поиск. Ты сразу отсекаешь огромную часть книг.

  • Filter (фильтр): А теперь ты достаешь всю огромную пачку карточек на «Толстой» и начинаешь вручную перебирать их в поисках той, где год издания равен заданному. Это уже дополнительная работа. Это все еще быстрее, чем обыскивать всю библиотеку, но медленнее, чем если бы у тебя был отдельный ящик для «Толстой, 1900».

Лена кивнула:

— Да это я понимаю. Index Cond — это навигация, Filter — это перебор. Но почему created_at ушло в Filter, если оно входит в мой индекс?

Макс развернул её ноутбук.

— Покажи запрос.

SELECT *
FROM orders
WHERE status = 'processed'
AND date_trunc('month', created_at) = '2025-08-01';

— Вот! — воскликнул он. — Ты ищешь не по created_at, а по функции от него. Индекс хранит точные значения created_at, но ничего не знает о результате date_trunc. Поэтому Postgres делает так:

  1. Index Cond: status = 'processed': Быстро находит по индексу все строки с нужным статусом.

  2. Filter: date_trunc('month', created_at) = ...: Для каждой найденной строки он вычисляет функцию и проверяет, подходит ли она под условие.

Когда ещё условие попадает в Filter?

  • Преобразование самого столбца: условие вроде WHERE phone::bigint = 79991234567 ищет уже по выражению, а не по исходному текстовому ключу. Без подходящего индекса по выражению прямой поиск по обычному индексу становится недоступен. Простая ошибка типов чаще вообще завершится сообщением об отсутствии оператора, а не тихим Seq Scan.

  • Поле в INCLUDE индекса: Если ты создала индекс ... ON t(a) INCLUDE (b), то поле b не является частью ключа поиска. Условие WHERE b = 5 уйдет в Filter.

  • Нет условия по ведущему столбцу: индекс (city, street) обычно хуже подходит для WHERE street = 'Ленина', чем индекс с street в начале. Конкретный план зависит от версии PostgreSQL, статистики и доступных способов сканирования, поэтому проверяйте его через EXPLAIN, а не только по правилу «левого префикса».

  • Частая ловушка — когда условия включаются через OR или NOT. Индекс использует только то, что соответствует структуре (обычно первое поле в ключе), а всё остальное фильтруется вручную.

— Так что же, я зря создавала составной индекс? — расстроилась Лена.

— Нет! Ты все еще можешь его использовать эффективно. Например, перепиши условие по created_at без использования функции:

SELECT *
FROM orders
WHERE status = 'processed'
AND created_at >= timestamp '2025-08-01'
AND created_at < timestamp '2025-09-01';

— Если условие по функции действительно является частью устойчивого контракта запросов, а created_at имеет тип timestamp without time zone, можно рассмотреть индекс по выражению:

CREATE INDEX ON orders (status, date_trunc('month', created_at));

С таким индексом оба твоих условия попадут в Index Cond, и Filter исчезнет.

Для timestamptz выражение должно однозначно фиксировать часовой пояс и быть допустимым для индексного выражения. На практике полуоткрытый диапазон обычно проще, понятнее и не захватывает полночь следующего месяца.

Что запомнить

  • Index Cond: Прямое использование индекса для навигации. Это высшая лига.

  • Filter: Дополнительная проверка строк, уже полученных после сканирования по Index Cond.

  • Появление Filter — это не катастрофа, а подсказка: возможно, твой индекс или запрос можно улучшить.