Что делать если запрос с фильтрацией по дате не использует индекс
Что проверяют
Понимаете ли вы, что планировщик выбирает план по стоимости и статистике, а не «забывает» про индекс. Умеете ли читать план выполнения и знаете ли, как ORM ломает индекс неявным приведением типа.
Как отвечать
Начать с плана, а не с догадок: EXPLAIN (ANALYZE, BUFFERS) на реальном объёме данных. Дальше разбирать по причинам:
- функция или каст на колонке:
WHERE date(created_at) = '2026-01-01'обычный индекс поcreated_atне задействует, лечится переписыванием в диапазон>= ... AND < ...либо индексом по выражению; - несовпадение типов: колонка
timestamptz, а параметр приходит строкой или датой, и приведение часового пояса делает условие неиндексируемым; - низкая селективность: условие выбирает заметную долю таблицы, и последовательное чтение честно дешевле;
- устаревшая статистика после массовой заливки, помогает
ANALYZE; - составной индекс, в котором дата стоит не там: до неё в списке колонок есть условие не на равенство;
ORDER BYсLIMITуводит планировщик в индекс по другой колонке.
Python-специфика: смотреть сгенерированный SQL, а не код ORM. В SQLAlchemy это echo=True или компиляция запроса в строку. Наивный datetime против timestamptz даёт тот самый неявный каст, а склейка даты в строку запроса вместо параметра лишает базу возможности переиспользовать план.
Где спотыкаются
Создают ещё один индекс, не посмотрев план. Новый индекс не подхватится по той же причине, что и старый, зато замедлит запись и займёт диск.
Считают последовательное чтение ошибкой планировщика. Если фильтр оставляет большую часть строк, случайные обращения по индексу дороже; ускоряет тут сужение выборки, покрывающий индекс или партиционирование по дате, а не уговоры планировщика.
Проверяют на пустой тестовой базе. На тысяче строк план всегда будет последовательным сканированием, и вывод «индекс не работает» переносят на прод, где картина обратная.