Как оптимизировать запрос, использующий фильтр?

Добрый день! Имеется упрощённая схема: таблица t с большим объёмом данных (включая поле _value) и таблица f с фильтрами, где пользователь переключает is_use (true|false). Данных в t много (~1 млн), фильтров до ~1 тыс., и требуется быстро получать записи t, удовлетворяющие текущим фильтрам. Индексы уже есть. Можно ли как-то ускорить или переписать запрос для повышения производительности? Ниже — DDL и текущий запрос:
  • 13 марта 2026 г. 8:01
Ответы на вопрос 1
Спасибо — давайте сначала проясним: пришлите, пожалуйста, DDL таблиц (точные типы и имеющиеся индексы), текущий SQL и результат EXPLAIN (ANALYZE) для этого запроса. Без этого можно дать только общие рекомендации; прислав EXPLAIN, вы получите конкретные правки.

Ниже — набор практических подходов и шаблонных приёмов, которые обычно сильно ускоряют такие сценарии. Стратегии отсортированы по простоте внедрения и отдаче.

1) Сначала профилируйте
- Выполните EXPLAIN (ANALYZE, BUFFERS) (Postgres) или EXPLAIN FORMAT=JSON (MySQL 8+) — это покажет, где тратится время.
- Посмотрите план: seq scan по большой таблице? heavy nested loop? много чтений диска?

2) Простейшая правильная форма запроса
- Если фильтры — это список значений для t._value, то простейшие эквиваленты:
  - JOIN: SELECT t.* FROM t JOIN f ON f.value = t._value WHERE f.is_use;
  - EXISTS: SELECT t.* FROM t WHERE EXISTS (SELECT 1 FROM f WHERE f.is_use AND f.value = t._value);
  - IN: SELECT * FROM t WHERE _value IN (SELECT value FROM f WHERE is_use);
Обычно JOIN/IN/EXISTS эквивалентны — но планировщик выбирает разный план. Попробуйте все и смотрите EXPLAIN.

3) Индексы — правильные и покрывающие
- Индекс на t(_value) обязателен.
- Индекс на f(value) или составной индекс f(is_use, value) полезен, чтобы быстро получить активные фильтры.
- Если вы выбираете только несколько столбцов из t, рассмотрите покрывающий индекс (include / INCLUDE cols в Postgres, или покрывающий индекс в MySQL) — чтобы избежать обращения к heap/row.

4) Если f маленькая (до ~1000) — делайте join с хешем
- Планер обычно сделает Hash Join (загружает f в память и пробивает t). Это быстро, если f помещается в память.
- Если же план делает Nested Loop и обращается по индексу к t много раз (медленно), проверьте настройку work_mem и оценки размера — возможно, увеличив work_mem, Postgres выберет Hash Join.

5) Если IN-list формируется на приложении
- Генерируйте WHERE _value IN (<список активных значений>) — для 1000 значений это допустимо. Но большие IN-листы могут ухудшать план и разработку.
- Альтернатива: передавать значения как временную таблицу/табличный параметр и JOIN к ней. Создайте TEMP TABLE active_filters(value) с индексом и JOIN — это часто быстрее и стабильнее.

6) Материализованный результат / кэш
- Если фильтры редко меняются (или пользователь нажимает «применить»), можно поддерживать материализованное представление / кэш: таблица active_t_ids, которая хранит id из t, удовлетворяющие текущим активным фильтрам. Обновлять её при изменении фильтров (полная перестройка или инкрементальное).
- Это даёт самые большие выигрыши при многократных запросах с одними и теми же фильтрами.

7) Частичные и составные индексы
- Если большинство запросов используют только строки t, где _value IN (много значений), можно создать индекс под эти значения: CREATE INDEX ON t(_value) — обычно достаточно.
- Для часто используемых конкретных фильтров можно сделать частичный индекс: CREATE INDEX ON t(cols) WHERE <условие>, если условие фиксированное и часто запрашивается.

8) Вынос вычислений/пре-компиляция
- Если фильтры — сложные выражения или диапазоны, попробуйте привязать их к полям, пригодным для индексации (нормализовать фильтры).
- Для JSON/array полей используйте GIN/GIN_TRGM индексы (Postgres) и соответствующие операторы.

9) Альтернативы для очень большого объёма / сложных комбинаций
- Roaring Bitmaps: для многократных быстрых пересечений/объединений множеств id — очень быстро, но требует дополнительной инфраструктуры.
- Inverted index / search engine (Elasticsearch) — если комбинаций фильтров очень много и отклик должен быть очень быстрым.

10) Практические «быстрые» правки для теста
- Сравните планы JOIN vs EXISTS vs IN.
- Попробуйте временно установить enable_seqscan = off (тестово) — если это улучшает план, значит надо поправить статистику/индексы.
- Проанализируйте таблицы: VACUUM ANALYZE / ANALYZE.
- Увеличьте work_mem для тестов, чтобы избежать диск/спетчинг во время хеширования.

11) Пример: temp table + индекс (Postgres / MySQL общий смысл)
- Создаёте temp table active_filters(value);
- Заполняете её значениями из f WHERE is_use;
- CREATE INDEX ON active_filters(value);
- SELECT t.* FROM t JOIN active_filters af ON af.value = t._value;
Этот подход даёт стабильный план и часто быстрее, чем подзапросы с большим IN-list.

12) Что ещё полезно прислать
- DDL (CREATE TABLE t, CREATE TABLE f), существующие индексы.
- Текущий SQL.
- EXPLAIN (ANALYZE) / время выполнения.
- Размеры таблиц (кол-во строк), кардинальность поля _value, распределение (сколько t строк на одно значение).
- Что именно считается «быстро» (SLA).

Если пришлёте эти данные, дам конкретный переписанный SQL и рекомендации по индексам/материализации и покажу ожидаемое улучшение.
Похожие вопросы