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

113

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

микрофон
источник
Дублировано, см. Stackoverflow.com/questions/14554302/…
Григорий Кислин
1
+1 Я хотел бы увидеть эту функцию. Это не просто отключение последовательного сканирования, как говорят другие ответы: нам нужна возможность заставить PG использовать определенный индекс . Это связано с тем, что на самом деле статистика может быть совершенно неправильной, и в этот момент вам нужно использовать ненадежные / частичные обходные пути. Я согласен с тем, что в простых случаях вы должны сначала проверить индексы и другие параметры, но для надежности и расширенного использования больших данных нам это нужно.
collimarco
У MySQL и Oracle есть это ... Не уверен, почему планировщик Postgres настолько ненадежен.
Кевин Паркер,

Ответы:

103

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

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

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

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

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

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

Патрик Кордылевски
источник
4
Согласен, принуждение postgres делать это по-своему обычно означает, что вы сделали это неправильно. В 9/10 раз планировщик превзойдет все, что вы придумаете. Другой раз это потому, что ты ошибся.
Кент Фредрик
Я думаю, что это хорошая идея для проверки действительно классов операторов вашего индекса.
metdos
2
Ненавижу возрождать старый вопрос, но я часто вижу в документации Postgres, обсуждениях и здесь, но есть ли общая концепция того, что подходит для маленькой таблицы ? Это что-то вроде 5000 строк или 50000 и т. Д.?
waffl
1
@waffl Думали ли вы о сравнительном анализе? Создайте простую таблицу с индексом и сопутствующей функцией для заполнения ее n строками случайного мусора. Затем начните изучать план запроса для различных значений n . Когда вы видите, что он начинает использовать индекс, у вас должен быть приблизительный ответ. Вы также можете получить последовательное сканирование, если PostgreSQL определит (на основе статистики), что сканирование индекса также не удалит очень много строк. Так что тестирование производительности всегда полезно, когда у вас есть реальные проблемы с производительностью. Как неофициальное предположение, я бы сказал, что пара тысяч обычно «мало».
jpmc26 09
11
Обладая более чем 30-летним опытом работы на таких платформах, как Oracle, Teradata и MSSQL, я считаю, что оптимизатор PostgreSQL 10 не особенно умен. Даже с актуальной статистикой он генерирует менее эффективные планы выполнения, чем принудительный в конкретном направлении. Предоставление структурных подсказок для компенсации этих проблем обеспечит решение, позволяющее PostgreSQL расширяться в большем количестве сегментов рынка. ПО МОЕМУ МНЕНИЮ.
Гвидо Лендерс
75

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

set enable_seqscan=false

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

Нирадж Бхавнани
источник
41
этот короткий ответ на самом деле дает хороший намек для целей тестирования
dwery 03
3
Никто не отвечает на вопрос!
Ивайло Бардаров
@IvailoBardarov Причина, по которой все эти другие предложения здесь, в том, что PostgreSQL не имеет этой функции; это было сознательное решение, принятое разработчиками, основанное на том, как он обычно используется, и на долгосрочных проблемах, которые оно вызывает.
jpmc26 09
Хороший трюк для тестирования: запустите set enable_seqscan=false, запустите свой запрос, а затем быстро запустите, set enable_seqscan=trueчтобы вернуть postgresql его правильное поведение (и, очевидно, не делайте этого в производственной
среде
3
@BrianHellekin Лучше, SET SESSION enable_seqscan=falseчтобы повлиять только на себя
Изката
20

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

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
Ziggy Crueltyfree Zeitgeister
источник
3
Хорошая идея. Однако, когда мы отключаем использование текущего индекса с помощью этого метода, оптимизатор запросов postgresql откатывается к следующему подходящему индексу. Таким образом, нет гарантии, что оптимизатор выберет your_wanted_index, возможно, что механизм postgresql просто выполнит вместо этого сканирование последовательности / первичного ключа. Заключение - не существует 100% надежного метода принудительного использования индекса для сервера PostgreSql.
Агниус Василяускас
Что делать, если нет whereусловия, кроме двух таблиц или соединенных, и Postgres не может принять индекс.
Луна Лавгуд
@Surya, вышесказанное относится как к WHERE, так и к JOIN ... ON,
Ziggy Crueltyfree Zeitgeister
18

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

Эта проблема обычно возникает, когда оценочная стоимость сканирования индекса слишком высока и не соответствует действительности. Вам может потребоваться уменьшить 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 для явного кэширования данных.


emkey08
источник
2
Мне даже пришлось установить random_page_cost = 0.1, чтобы сканирование индекса работало на больших (таблица ~ 600M строк) на Pg 10.1 в Ubuntu. Без настройки последовательное сканирование (несмотря на параллельность) занимало 12 минут (обратите внимание, что была выполнена таблица анализа!). Привод - SSD. После настройки время выполнения стало 1 секунда.
Анатолий Алексеев
Ты спас мне день. Я сходил с ума, пытаясь выяснить, как один и тот же запрос к той же базе данных занимал 30 секунд на одной машине и меньше 1 на другой, даже после выполнения анализа на обоих концах ... Кого это может касаться: команда ' ALTER SYSTEM SET random_page_cost = x 'устанавливает новое значение по умолчанию глобально.
Жюльен,
10

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

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

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

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

user80168
источник
2

Существует трюк, чтобы заставить postgres предпочесть seqscan, добавив OFFSET 0в подзапрос

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

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

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

Энтони Гиббс
источник
Хороший трюк. Хотя хороший оптимизатор, конечно, должен оптимизировать смещение 0 :-)
Гвидо Лендерс