Короткий ответ
Составной B-tree индекс упорядочен лексикографически — сначала по первой колонке, затем по второй внутри одинаковых значений первой, поэтому он эффективно используется для условий по первой колонке (или по обеим), но обычно бесполезен для запроса, фильтрующего только по второй колонке без первой.
Как это работает подробнее
Структура похожа на отсортированный список пар (a, b) — найти диапазон по a быстро, а внутри найденного диапазона дополнительно быстро сузить по b; без фиксации a СУБД не может перейти сразу к нужным b, они «разбросаны» по дереву.
Пример кода
example.sql
CREATE INDEX idx_orders_user_created ON orders(user_id, created_at);
-- Эффективно использует индекс: равенство по первой колонке + диапазон по второй
EXPLAIN SELECT * FROM orders WHERE user_id = 42 AND created_at > now() - interval '7 days';
-- Индекс почти не поможет: нет условия на первую колонку user_id
EXPLAIN SELECT * FROM orders WHERE created_at > now() - interval '7 days';Что использовать на практике
- Практика. Общее правило — ставить первой колонку с более высокой избирательностью или ту, по которой чаще фильтруют в равенстве (=), а колонку с диапазонным условием (<, >, BETWEEN) обычно ставят последней.
Подводные камни
- Ошибка. Создают отдельные индексы на a и на b по отдельности, думая, что это эквивалентно составному индексу (a, b) для запросов с условием на оба поля; меняют порядок колонок «для красоты», не проверяя профиль запросов через pg_stat_statements.