«Индекс есть, а не используется» чаще всего значит, что условие не-sargable: колонка спрятана под функцией или кастом.
Каст слева убивает индекс
-- не-sargable: индекс по created не подходит → Seq Scan
WHERE created::date = '2024-01-01'
-- sargable: диапазон по самой колонке → индекс работает
WHERE created >= '2024-01-01' AND created < '2024-01-02'
created::date оборачивает колонку в функцию; индекс построен по
значениям created, а сравнивается результат приведения.
Функция над колонкой - то же
WHERE lower(email) = 'a@b.c' -- индекс по email не подходит
Два выхода: переписать без функции или индекс по выражению:
CREATE INDEX ON users (lower(email));
Sargability ортогональна operator class
Даже если оператор поддержан методом доступа (см. operator-classes), не-sargable условие индекс не использует. Сначала условие должно быть sargable (колонка не под функцией), потом оператор - в operator class.
Как заметить
В EXPLAIN ANALYZE - Seq Scan (см. access-methods) там, где ждёшь
индекс, и большое расхождение estimated/actual rows. Типичные виновники: каст, функция,
неявное приведение типа. Проектирование индексов - в index-design.