Какой индекс построить для запроса с условиями на несколько столбцов
Что проверяют
Знаете ли вы правило левого префикса и умеете ли выбрать порядок колонок под конкретный запрос, а не по общему совету. Следом почти всегда дают запрос с ORDER BY и LIMIT и просят подобрать индекс, который обойдётся без сортировки.
Как отвечать
Базовый ответ: один составной B-tree индекс под запрос вместо нескольких одиночных. PostgreSQL умеет объединять одиночные индексы через BitmapAnd, но это дороже одного прохода по составному.
Порядок колонок решает всё:
- сначала равенства, потом диапазон: для
WHERE user_id = $1 AND status = $2 AND created_at > $3индекс(user_id, status, created_at). Колонки после диапазонной поиск почти не сужают; - левый префикс: индекс
(a, b, c)помогает запросам поa, поa, bи по всем трём, а запросу только поbпочти нет. В PostgreSQL 18 появился skip scan, который частично закрывает этот случай, если в первой колонке мало разных значений, но проектировать под него не стоит; - сортировка: для
WHERE user_id = $1 ORDER BY created_at DESC LIMIT 20индекс(user_id, created_at DESC)отдаёт первые 20 строк сразу; - частичный индекс: постоянное условие вроде
deleted_at IS NULLвыносят вWHEREиндекса, он становится меньше; - покрывающий:
INCLUDE (amount)позволяет ответить без чтения таблицы.
Селективность важна, но вторична: сначала смотрите, какие запросы используют индекс и где у них равенство, а где диапазон.
В Go-проекте индекс создаётся не кодом приложения, а миграцией (goose, golang-migrate), на живой таблице через CREATE INDEX CONCURRENTLY. Результат проверяют EXPLAIN (ANALYZE, BUFFERS) на запросе с реальными параметрами.
Где спотыкаются
Ставят первой самую селективную колонку, даже если по ней идёт диапазон. Тогда колонки с равенством после неё индекс почти не сужают.
Создают по индексу на каждую колонку и ждут, что база объединит их не хуже составного.
Строят индексы на все комбинации колонок. Каждый лишний индекс замедляет запись и занимает место, а часть из них покрывается левым префиксом другого.