Когда использовать составные индексы?
Что проверяют
Понимаете ли вы, как устроен составной B-tree индекс и почему порядок колонок в нём решает всё. Следом обычно дают запрос и просят назвать индекс под него, а потом спрашивают, как проверить, что база его использует.
Как отвечать
Составной индекс отсортирован по первой колонке, внутри неё по второй и так далее. Отсюда правило левого префикса: индекс (user_id, created_at) работает для фильтра по user_id и по паре, но не для фильтра только по created_at.
Когда он нужен:
- фильтр по нескольким колонкам:
WHERE user_id = %s AND status = 'paid'на большой таблице; - фильтр плюс сортировка:
WHERE user_id = %s ORDER BY created_at DESC LIMIT 20. Индекс(user_id, created_at)отдаёт строки уже упорядоченными, отдельной сортировки в плане нет; - уникальность комбинации: в Django это
UniqueConstraintвMeta.constraints, документация советует его вместоunique_together; - покрывающий индекс: в PostgreSQL через
INCLUDE, чтобы запрос обошёлся index only scan без чтения таблицы.
Порядок колонок: сначала поля с условием равенства, затем поле диапазона или сортировки. Для WHERE user_id = %s AND created_at > %s индекс (created_at, user_id) заметно хуже: база пройдёт весь диапазон дат и будет проверять user_id в каждой записи.
В коде это models.Index(fields=["user", "-created_at"]) в Meta.indexes у Django или Index("ix_orders_user_created", "user_id", "created_at") в SQLAlchemy. Проверка одна: EXPLAIN ANALYZE на реальном объёме данных.
Где спотыкаются
Ставят db_index=True на каждое поле и считают, что это то же самое. Два одиночных индекса PostgreSQL иногда объединит через bitmap, но это дороже одного составного и сортировку не даёт.
Повторяют «самая селективная колонка первой» как закон. Важнее, какие условия в запросах: равенство или диапазон, и какие ещё запросы должны пользоваться тем же индексом.
Держат рядом составной (a, b) и одиночный по a. Второй почти всегда лишний, а платить за него приходится на каждой записи. Skip scan из PostgreSQL 18 тоже не повод забыть про порядок: он выручает, только когда у первой колонки мало различных значений.