У меня есть относительно простой запрос к таблице с 1,5M строк:
SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;
EXPLAIN ANALYZE
вывод:
Limit (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1) -> Bitmap Heap Scan on publication (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1) Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321)) -> BitmapOr (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1) -> Bitmap Index Scan on publication_pkey (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1) Index Cond: (mtid = 9762715) -> Bitmap Index Scan on publication_last_modifier_btree (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1) Index Cond: (last_modifier = 21321) Total runtime: 1.027 ms
Пока все хорошо, быстро и использует доступные индексы.
Теперь, если я немного изменю запрос, результат будет:
SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;
EXPLAIN ANALYZE
Выход:
Limit (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1) -> Seq Scan on publication (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1) Filter: ((hashed SubPlan 1) OR (last_modifier = 21321)) SubPlan 1 -> Result (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1) Total runtime: 2841.442 ms
Не так быстро, и с помощью seq scan ...
Конечно, исходный запрос, выполняемый приложением, немного сложнее и даже медленнее, и, конечно, генерируемый в спящем режиме нет (SELECT 9762715)
, но медлительность есть даже для этого (SELECT 9762715)
! Запрос генерируется hibernate, поэтому изменить их довольно сложно, а некоторые функции недоступны (например, UNION
недоступны, что было бы быстро).
Вопросы
- Почему индекс не может быть использован во втором случае? Как они могли быть использованы?
- Могу ли я улучшить производительность запросов другим способом?
Дополнительные мысли
Кажется, что мы могли бы использовать первый случай, вручную выполнив SELECT, а затем поместив полученный список в запрос. Даже с 5000 числами в списке IN () это в четыре раза быстрее, чем второе решение. Однако, это только кажется НЕПРАВИЛЬНЫМ (также, это могло бы быть в 100 раз быстрее :)). Совершенно непонятно, почему планировщик запросов использует совершенно разные методы для этих двух запросов, поэтому я хотел бы найти более удачное решение этой проблемы.
JOIN
вместоIN ()
? Кроме того,publication
был недавно проанализирован?(SELECT 9762715)
.(SELECT 9762715)
. На вопрос гибернации: это можно сделать, но требует серьезного переписывания кода, поскольку у нас есть пользовательские критерии запроса гибернации, которые транслируются на лету. Поэтому, по сути, мы будем модифицировать Hibernate, что является огромным предприятием с множеством возможных побочных эффектов.Ответы:
Суть проблемы становится очевидной:
Оценки Postgres вернут 744661 рядов, в то время как, на самом деле, это один ряд. Если Postgres не знает лучше, чего ожидать от запроса, он не может планировать лучше. Нам нужно увидеть фактический запрос, спрятанный за ним,
(SELECT 9762715)
и, возможно, также знать определение таблицы, ограничения, количество элементов и распределение данных. Очевидно, Postgres не в состоянии предсказать , как несколько строк будут возвращены им. Там могут быть способы переписать запрос, в зависимости от того, что он является .Если вы знаете, что подзапрос не может вернуть больше
n
строк, вы можете просто сообщить Postgres, используя:Если n достаточно мало, Postgres переключится на (растровое) сканирование индекса. Однако это работает только для простого случая. Перестает работать при добавлении
OR
условия: планировщик запросов в настоящее время не может справиться с этим.Я редко использую
IN (SELECT ...)
для начала. Обычно есть лучший способ реализовать то же самое, часто сEXISTS
полусоединением. Иногда с (LEFT
)JOIN
(LATERAL
) ...Очевидным обходным путем будет использование
UNION
, но вы исключили это. Я не могу сказать больше, не зная фактического подзапроса и других соответствующих деталей.источник
(SELECT 9762715)
! Если я выполню именно тот запрос, который вы видите выше. Конечно, исходный запрос гибернации немного сложнее, но мне (кажется, мне) удалось точно определить, где планировщик запросов сбивается, поэтому я представил эту часть запроса. Тем не менее, вышеизложенное объясняет и запросы дословно ctrl-cv.EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;
также выполняется последовательное сканирование, а также выполняется около 3 секунд ...CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;
. И эффект все еще был там для тех же самых запросовtest
: любой подзапрос приводил к сканированию seq ... Я пробовал и 9.1 и 9.4. Эффект тот же.OR
условия. Трюк сLIMIT
работает только для более простого случая.Мой коллега нашел способ изменить запрос так, чтобы он нуждался в простом переписывании и делал то, что ему нужно, то есть выполнял отбор за один шаг, а затем выполнял дальнейшие операции с результатом:
Объяснить анализ сейчас:
Кажется, мы можем создать простой синтаксический анализатор, который находит и переписывает все подвыборы таким образом, и добавить его в спящий режим, чтобы манипулировать собственным запросом.
источник
SELECT
s, как у вас в первом запросе в вопросе?SELECT
отдельно, а затем сделать внешний выбор со статическим списком послеIN
. Однако это значительно медленнее (в 5-10 раз, если подзапрос имеет более нескольких результатов), так как у вас есть дополнительные обходы по сети, плюс вы форматируете postgres много результатов, а затем анализируете эти результаты Java (а затем делаете то же самое снова в обратном направлении). Решение выше делает то же самое семантически, оставляя процесс внутри postgres. В общем, в настоящее время это кажется самым быстрым способом с минимальной модификацией в нашем случае.Ответьте на второй вопрос: Да, вы можете добавить ORDER BY в свой подзапрос, что окажет положительное влияние. Но это похоже на решение «EXISTS (подзапрос)» по производительности. Существует значительная разница даже с подзапросом, приводящим к двум строкам.
источник