Как заставить 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 раз быстрее.
Ответ 7
Продукт EnterpriseDB PostgresPlus Advanced Server поддерживает синтаксис подсказок Oracle, хотя этот продукт не является бесплатным.