Как создать индекс с фильтром для индексации пользователей старше 18 лет
Что проверяют
Знаете ли вы частичные индексы и понимаете ли, когда планировщик их реально использует. Сильный кандидат сразу замечает подвох: возраст меняется со временем, и хранить его колонкой плохая идея. Следом спрашивают, как это работает с параметризованными запросами из Go.
Как отвечать
Синтаксис частичного индекса в PostgreSQL: CREATE INDEX idx_users_adult ON users (age) WHERE age >= 18. В индекс попадают только строки под условием, поэтому он меньше и дешевле при записи. В SQL Server это называется filtered index, в MySQL частичных индексов нет.
Дальше разберите модель данных:
- возраст колонкой: устаревает каждый день, его придётся пересчитывать фоновой задачей. Правильно хранить
birth_date; - условие по дате в индексе:
WHERE birth_date <= now() - interval '18 years'написать нельзя, в условии индекса допустимы только IMMUTABLE-функции, аnow()к ним не относится; - что делать: обычный индекс по
birth_dateи запрос с порогом, вычисленным в Go черезtime.Now().AddDate(-18, 0, 0). Частичный индекс тогда строят по стабильному признаку, напримерWHERE deleted_at IS NULL; - когда частичный индекс оправдан: когда условие отбирает малую долю строк. Если совершеннолетних 95%, выигрыш по размеру почти нулевой.
Важная деталь для Go. Планировщик берёт частичный индекс, только если может доказать, что условие запроса попадает в условие индекса. В pgx запросы по умолчанию подготавливаются и кэшируются, и после нескольких выполнений PostgreSQL может перейти на обобщённый план, где age >= $1 уже не доказывает age >= 18. Константу из условия индекса надёжнее писать в SQL литералом. Сам индекс создаётся миграцией с CREATE INDEX CONCURRENTLY, в goose такой файл помечают -- +goose NO TRANSACTION.
Где спотыкаются
Пишут age > 18 и теряют ровно восемнадцатилетних. «Старше 18» в бизнес-смысле обычно значит «совершеннолетний», то есть >= 18.
Создают индекс прямо из кода приложения через db.Exec при старте. Схема должна меняться миграциями, иначе несколько реплик сервиса будут конкурировать за одну операцию.
Не проверяют план. Без EXPLAIN вы не узнаете, что запрос с параметром идёт мимо индекса.