Как заставить Postgres использовать определенный индекс?

Как заставить Postgres использовать индекс, если он иначе настаивал бы на последовательном сканировании?

Ответ 1

Предполагая, что вы спрашиваете об общей "подсказке индекса", обнаруженной во многих базах данных, PostgreSQL не предоставляет такую ​​функцию. Это было сознательное решение, принятое командой PostgreSQL. Хороший обзор того, почему и что вы можете сделать, можно найти здесь. Причинами являются, в основном, то, что это взлом производительности, который, как ваши данные, вызывает больше проблем в дальнейшем, тогда как оптимизатор PostgreSQL может переоценить план на основе статистики. Другими словами, то, что может быть хорошим планом запроса сегодня, вероятно, не будет хорошим планом запросов на все время, а подсказки индексов заставляют конкретный план запросов на все время.

Как очень тупой молот, полезный для тестирования, вы можете использовать параметры enable_seqscan и enable_indexscan. См:

Они не подходят для текущего производства. Если у вас есть проблемы с выбором плана запроса, вы должны увидеть документацию для отслеживания проблем с производительностью запросов. Не просто установите параметры enable_ и уходите.

Если у вас есть очень веская причина для использования индекса, Postgres может сделать правильный выбор. Почему?

  • Для небольших таблиц быстрее выполнять последовательные проверки.
  • Postgres не использует индексы, когда типы данных не соответствуют должным образом, вам может потребоваться включить соответствующие приведения.
  • Настройки вашего планировщика могут вызвать проблемы.

См. также этот старый пост в новостной группе.

Ответ 2

Вероятно, единственная действительная причина использования

set enable_seqscan=false

- это когда вы пишете запросы и хотите быстро увидеть, что на самом деле был бы план запроса, там были большие объемы данных в таблице (таблицах). Или, конечно, если вам нужно быстро подтвердить, что ваш запрос не использует индекс просто потому, что набор данных слишком мал.

Ответ 3

Иногда PostgreSQL не делает лучший выбор индексов для определенного условия. В качестве примера предположим, что есть таблица транзакций с несколькими миллионами строк, из которых несколько сотен за любой день, а таблица имеет четыре индекса: transaction_id, client_id, дату и описание. Вы хотите запустить следующий запрос:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description = 'Refund'
GROUP BY client_id

PostgreSQL может использовать индекс transaction_description_idx вместо transaction_date_idx, что может привести к тому, что запрос займет несколько минут, а не менее одной секунды. Если это так, вы можете принудительно использовать индекс по дате, вымачивая условие следующим образом:

SELECT client_id, SUM(amount)
FROM transactions
WHERE date >= 'yesterday'::timestamp AND date < 'today'::timestamp AND
      description||'' = 'Refund'
GROUP BY client_id

Ответ 4

Краткий ответ

Эта проблема обычно возникает, когда оценочная стоимость сканирования индекса слишком высока и не соответствует действительности. Вам может потребоваться уменьшить параметр конфигурации random_page_cost, чтобы это исправить. Из документации Postgres:

Уменьшение этого значения [...] приведет к тому, что система предпочтет сканирование индекса; его повышение сделает просмотр индекса относительно более дорогим.

Вы можете проверить, действительно ли более низкое значение заставит Postgres использовать индекс (но используйте его только для тестирования):

EXPLAIN <query>;              # Uses sequential scan
SET random_page_cost = 1;
EXPLAIN <query>;              # May use index scan now

Вы можете восстановить значение по умолчанию с помощью SET random_page_cost = DEFAULT;.

Фон

Сканирование индекса требует непоследовательного извлечения страниц диска. Postgres использует random_page_cost для оценки стоимости таких непоследовательных выборок по сравнению с последовательными выборками. Значение по умолчанию - 4.0, что предполагает средний коэффициент затрат 4 по сравнению с последовательными выборками (с учетом эффектов кэширования).

Проблема, однако, заключается в том, что это значение по умолчанию не подходит для следующих важных реальных сценариев:

1) Твердотельные накопители

Как признается в документации:

Хранилище, которое имеет низкую стоимость случайного чтения по сравнению с последовательным, например Твердотельные накопители могут быть лучше смоделированы с более низким значением для random_page_cost.

Согласно последнему пункту этого слайда из выступления на PostgresConf 2018, для random_page_cost должно быть установлено что-то среднее между 1.0 и 2.0 для твердотельных накопителей.

2) Кэшированные данные

Если необходимые данные индекса уже кэшированы в ОЗУ, сканирование индекса всегда будет значительно быстрее, чем последовательное сканирование. В документации сказано:

Соответственно, если ваши данные, скорее всего, будут полностью в кеше, может оказаться целесообразным [...] уменьшение random_page_cost.

Проблема в том, что вы, конечно, не можете легко узнать, кэшированы ли соответствующие данные. Однако, если к конкретному индексу часто обращаются с запросом и если в системе достаточно ОЗУ, данные, вероятно, будут кэшироваться, и для random_page_cost должно быть установлено более низкое значение. Вам придется поэкспериментировать с различными значениями и посмотреть, что работает для вас.

Вы также можете использовать расширение pg_prewarm для явного кэширования данных.


Ответ 5

Вопрос о себе очень недействителен. Принуждение (например, enable_seqscan = off) - очень плохая идея. Возможно, было бы полезно проверить, будет ли он быстрее, но производственный код никогда не должен использовать такие трюки.

Вместо этого - объясните анализ вашего запроса, прочитайте его и узнайте, почему PostgreSQL выбирает плохой (по вашему мнению) план.

В Интернете есть инструменты, которые помогают с чтением объяснить анализ вывода - один из них - explain.depesz.com - написанный мной.

Другой вариант - присоединиться к каналу #postgresql в freenode сети irc и поговорить с ребятами, чтобы помочь вам - как оптимизировать запрос это не вопрос "задайте вопрос, получите ответ, чтобы быть счастливым". это больше похоже на разговор, со многими вещами, чтобы проверить, много вещей, которые нужно изучить.

Ответ 6

Есть хитрость, чтобы подтолкнуть postgres, чтобы предпочесть seqscan, добавив OFFSET 0 в подзапрос

Это удобно для оптимизации запросов, связывающих большие/огромные таблицы, когда все, что вам нужно, это только n первых/последних элементов.

Допустим, вы ищете первые/последние 20 элементов, включающие в себя несколько таблиц, содержащих 100 тыс. (Или более) записей, не нужно строить/связывать весь запрос по всем данным, когда то, что вы будете искать, находится в первых 100 или 1000 записей. Например, в этом сценарии последовательное сканирование выполняется более чем в 10 раз быстрее.

см. Как я могу запретить Postgres встраивать подзапрос?

Ответ 7

Продукт EnterpriseDB PostgresPlus Advanced Server поддерживает синтаксис подсказок Oracle, хотя этот продукт не является бесплатным.